自己設計一個網(wǎng)頁優(yōu)化關鍵詞的方法有哪些
useState
useState用于在函數(shù)組件中聲明和管理狀態(tài)
它接受初始狀態(tài),并返回一個狀態(tài)變量和一個更新狀態(tài)的函數(shù)
通過調(diào)用更新狀態(tài)的函數(shù),可以改變狀態(tài)的值并觸發(fā)組件的重新渲染
import { useState } from "react"function App() {const [obj, setObj] = useState({a: 1,b: 2})const [arr, setArr] = useState([])const updateObjValue = () => {// 更新引用值的狀態(tài),必須傳遞一個新的引用setObj(prev => {return {...obj,b: 3}})setArr(prev => [...arr, 1])}return (<div onClick={updateObjValue}>a: {obj.a}b: {obj.b}數(shù)組:{arr}</div>)
}export default App;
useEffect
React的useEffect鉤子可以讓開發(fā)者在函數(shù)組件中管理副作用。
副作用操作是指那些與組件渲染無關的操作,例如訪問網(wǎng)絡、獲取瀏覽器窗口大小、訪問本地存儲等。這些操作一般會產(chǎn)生一些副作用,例如更新組件狀態(tài)、改變?yōu)g覽器地址等。
使用useEffect可以在組件渲染后執(zhí)行副作用操作,并且可以根據(jù)需要設置條件,只在滿足條件時執(zhí)行副作用操作,從而避免不必要的計算和網(wǎng)絡請求。
useEffect接收兩個參數(shù):第一個參數(shù)是一個回調(diào)函數(shù),該函數(shù)會在組件渲染之后執(zhí)行,用于執(zhí)行副作用操作。
如果需要在組件卸載時執(zhí)行一些清理操作,可以在該函數(shù)中返回一個清理函數(shù),React會在組件卸載時調(diào)用該函數(shù)(如清除定時器等)。
第二個參數(shù)是一個可選數(shù)組,用于指定當依賴項發(fā)生變化時是否需要重新執(zhí)行effect函數(shù)。
如果第二個參數(shù)為空數(shù)組,則表示effect只會在組件掛載和卸載時執(zhí)行一次;如果第二個參數(shù)不為空數(shù)組,每當指定的依賴項發(fā)生變化時,effect函數(shù)都會重新執(zhí)行。
// 監(jiān)聽count的變化,重新渲染
import React, { useState, useEffect } from 'react';function MyComponent(props) {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;}, [count]);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}
請求網(wǎng)絡
import { useState, useEffect } from 'react';function Example() {const [data, setData] = useState(null);useEffect(() => {fetch('https://api.example.com/data').then(response => response.json()).then(data => setData(data));}, []);return (<div>{data && <h1>{data.title}</h1>}</div>);
}
通過useEffect來獲取數(shù)據(jù)
import {useEffect, useState} from "react";const UseEffectDemo = () => {const [list, setList] = useState([])const [id, setId] = useState('5433d5e4e737cbe96dcef312')const [num, setNum] = useState(0)const [info, setInfo] = useState([])useEffect(()=>{const requestList = async () => {try {const response = await fetch('https://cnodejs.org/api/v1/topics')const res = await response.json()console.log(res['data'])setList(res['data'])} catch (err) {if (err) throw Error}}requestList().then((rs)=>{console.log('requestList')})},[])useEffect(()=>{const requestInfo = async () => {try {const response = await fetch('https://cnodejs.org/api/v1/topic/' + id)const res = await response.json()setInfo(res['data'])} catch (err) {if (err) throw Error}}requestInfo().then((rs)=>{console.log('requestInfo')})},[id])return (<><h2 id='title'>UseEffect Demo</h2><h4>{id}</h4><div style={{display:'flex'}}><ul>{list.map((item, index)=>{return (<likey={item.id}style={{background:index === num ? 'skyblue':'white', cursor:"pointer"}}onClick={()=>{setId(item.id)setNum(index)}}>{item.id}</li>)})}</ul><div dangerouslySetInnerHTML={{__html: info.content}}></div></div></>)
}
export default UseEffectDemo
useEffect是異步執(zhí)行的,useLayoutEffect是同步執(zhí)行的。
useLayoutEffect是當瀏覽器把內(nèi)容渲染到頁面之前執(zhí)行,而useEffect是當瀏覽器把內(nèi)容渲染到頁面之后執(zhí)行。盡可能使用標準的useEffect以避免阻塞頁面更新。
useEffect的運行規(guī)則:組件首次渲染工作完成并將真實dom生成到頁面以后,將對應的回調(diào)函數(shù)推入異步隊列等待執(zhí)行
useLayoutEffect的運行規(guī)則:組件首次渲染工作完成并將真實dom生成到頁面以后,將對應的回調(diào)函數(shù)推入同步隊列等待執(zhí)行
正常情況全部使用useEffect,只有逼不得已的情況才考慮是否使用useLayoutEffect
import { useState, useEffect } from "react";export default function TopicList() {const [topicList, setTopicList] = useState([])useEffect(() => {const requestList = async () => {try {const response = await fetch('https://cnodejs.org/api/v1/topics')const res = await response.json()console.log(res['data'])setTopicList(res['data'])} catch (err) {if (err) throw Error}}requestList().then((rs)=>{console.log('requestList')})const topicListDom = document.getElementsByClassName("topic-list-wrapper")[0]console.log('topicListDom', topicListDom)}, [])return (<div className="topic-list-wrapper">{topicList.map(topic => (<div key={topic.id}>{topic.title}</div>))}</div>)
}
清除定時器
import { useState, useEffect } from "react";export default function Tick() {const [tickTime, setTickTime] = useState(100)useEffect(() => {let timer = setInterval(() => {console.log("tickTime working")setTickTime(prev => prev - 1)}, 1000);return () => {clearInterval(timer)timer = null}}, [])return (<div>倒計時: {tickTime}</div>)
}
useReducer
useReducer是另一種讓函數(shù)組件保存狀態(tài)的方式
它接受一個 reducer
函數(shù)和一個初始狀態(tài),并返回當前狀態(tài)和 dispatch
函數(shù)
import { useReducer } from "react";export default function ReducerDemo() {const reducer = (state, action) => {switch (action.type) {case 'increment':return state + 1case 'decrement':return state + 1 }return state;}const [count, dispatch] = useReducer(reducer, 0)return (<div><span>{count}</span><button onClick={() => dispatch({ type: 'increment' })}>plus</button></div>)
}
useCallBack
每次組件的重新渲染都意味著內(nèi)部所有的引用值都會被重新構建
useCallBack用來長期穩(wěn)定的維護某一個函數(shù)的引用,它會將函數(shù)創(chuàng)建后的引用保存,當函數(shù)組件下一次重新渲染時,它會直接返回之前保存的引用,而不是重新創(chuàng)建引用
useCallBack只在創(chuàng)建函數(shù)引用的時候使用
import {useState, memo, useCallback} from "react";const Child = memo(({value,change}) => {console.log('----re-render----')return (<input type="text" value={value} onChange={change}/>)
})const UseCallback = () => {const [v1, setV1] = useState('')const [v2, setV2] = useState('')const onChange1 = useCallback((e)=>{setV1(e.target.value)}, [])const onChange2 = useCallback((e)=>{setV2(e.target.value)}, [])return (<><h2>UseCallback</h2><Child value={v1} change={onChange1}/><Child value={v2} change={onChange2}/></>)
}
export default UseCallback
useMemo
useMemo類似于計算屬性
useCallBack就是useMemo實現(xiàn)的,用來做緩存的
useCallBack第一個參數(shù)也是一個函數(shù),并不會在聲明時被React直接執(zhí)行;該函數(shù)會在組件渲染過程中被 useMemo 鉤子調(diào)用,將計算結果進行緩存
第二個參數(shù)是依賴項,當依賴項變化時,React會重新執(zhí)行對應的第一個參數(shù),然后拿到最新的返回值,再次進行緩存
業(yè)內(nèi)都是使用useCallBack去緩存函數(shù)
memo方法:
性能優(yōu)化,如果本組件中的數(shù)據(jù)沒有發(fā)生變化,阻止組件更新
import { memo } from "react";function MemoDemo() {console.log('memo rendering...')return (<div>memo data</div>)
}export default memo(MemoDemo)
自定義hooks
使用自定義 hook 時,需要遵循以下規(guī)則:
- 自定義 hook 必須是一個函數(shù),名稱必須以“use”為前綴。
- 自定義 hook 可以調(diào)用其他鉤子或普通函數(shù)。
- 自定義 hook 應該使用 useState、useEffect 等 React 鉤子創(chuàng)建狀態(tài)和處理副作用。
- 可以將自定義鉤子與 useContext 和 useReducer 配合使用來實現(xiàn)更復雜的行為。
下面是一個示例自定義 hook,用于檢查用戶是否擁有特定的權限:
import { useState, useEffect } from 'react';function useUserPermissions(permission) {const [hasPermission, setHasPermission] = useState(false);useEffect(() => {// 模擬 Ajax 請求,獲取用戶權限const userPermissions = ['admin', 'editor'];if (userPermissions.includes(permission)) {setHasPermission(true);} else {setHasPermission(false);}}, [permission]);return hasPermission;
}
在其他組件中使用 hook 檢查用戶是否擁有訪問特定頁面或功能的權限
import React from 'react';
import useUserPermissions from './useUserPermissions';function Dashboard() {const hasAccess = useUserPermissions('admin');return (<div>{hasAccess ? (<h1>Welcome to the Admin Dashboard!</h1>) : (<h1>Sorry, you do not have access to this page.</h1>)}</div>);
}
顯示loading效果
新建文件useRequestLoadingDispatcher.js
import { useState } from "react";export default function useRequestLoadingDispatcher() {const [loading, setLoading] = useState(false)const executeRequest =async (promiseFn) => {setLoading(true)console.log('loading data........................')await promiseFn()setLoading(false)console.log('init finish')}return {loading, executeRequest}
}
使用useRequestLoadingDispatcher
import { useState, useEffect } from "react";
import TopicItem from "../TopicItem";
import useRequestLoadingDispatcher from "../hooks/useRequestLoadingDispatcher";export default function TopicList() {const [topicList, setTopicList] = useState([])const {loading, executeRequest} = useRequestLoadingDispatcher()const fetchFromServer = async () => {// 延時模擬加載中const delay = async (duration = 2000) => {return new Promise((resolve, reject) => {setTimeout(() => {resolve()}, duration)})}executeRequest(async () => {try {const response = await fetch('https://cnodejs.org/api/v1/topics')const res = await response.json()console.log(res['data'])await delay(2000)setTopicList(res['data'])} catch (err) {if (err) throw Error}})} useEffect(() => {fetchFromServer()}, [])return (<div className="topic-list-wrapper">{ loading ? <div>數(shù)據(jù)加載中</div> : (topicList.map(topic => (<TopicItem {...topic}></TopicItem>)))}</div>)
}
強制刷新useForceUpdate
import { useState } from "react";export default function useForceUpdate() {const [_, setForceObj] = useState({})const forceUpdate = () => {setForceObj({})}return forceUpdate;
}
使用強制刷新
import useForceUpdate from "../hooks/useForceUpdate"
export default function ForceUpdateTest() {console.log('do force update')const forceUpdate = useForceUpdate()return (<button onClick={forceUpdate}>force update</button>)
}
useRef
構建一個狀態(tài)出來,但是這個狀態(tài)是直接脫離React控制的,也不會造成頁面的重新渲染,同時狀態(tài)還不會因為組件的重新渲染而被初始化
import { useState, useCallback, useRef } from "react";
export default function Ticker() {const [timeCount, setTimeCount] = useState(60)const timerIdRef = useRef(null)// useRef會返回一個對象,里面有一個current屬性// ref是可讀可寫的console.log("timerIdRef", timerIdRef)const startTick = useCallback(() => {timerIdRef.current = setInterval(()=>{setTimeCount(prev => prev - 1)}, 1000)})const stopTick = useCallback(() => {if (timerIdRef.current) clearInterval(timerIdRef.current)})return (<><button onClick={startTick}>start</button><button onClick={stopTick}>stop</button><span>{timeCount}</span></>)
}
使用useRef獲取DOM元素
import { useCallback, useEffect, useRef } from "react"export default function TestInput() {const inputElementRef = useRef(null)useEffect(() => {inputElementRef.current = document.getElementsByClassName('input-example')[0]}, [])const handleClick = useCallback(() => {inputElementRef.current.focus()})return (<div><input className="input-example"></input><button onClick={handleClick}>click me</button></div>)
}
useRef可以直接作用在dom上,它使用useEffect去獲取真實dom并且賦值
import { useCallback, useRef } from "react"export default function TestInput() {const inputElementRef = useRef(null)const handleClick = useCallback(() => {inputElementRef.current.focus()})return (<div><input ref={inputElementRef} className="input-example"></input><button onClick={handleClick}>click me</button></div>)
}
fowardRef
fowardRef是一個高階組件
高階組件:接收一個組件作為參數(shù),返回一個新的組件
fowardRef給函數(shù)組件擴展了一個ref屬性
TestInput.jsx
import { forwardRef } from "react"// 給組件掛載ref屬性,必須使用forwardRef
function TestInput(props, parentRef) {return (<div><input ref={parentRef} className="input-example"></input></div>)
}export default forwardRef(TestInput)
App.js
forwardRef一般都是和組件ref連用的,不會單獨使用
import { useCallback, useRef } from "react";
import TestInput from "./components/TestInput";function App() {const testInputRef = useRef(null)const handleClick = useCallback(() => {testInputRef.current.focus()})return (<div><TestInput ref={testInputRef}/><button onClick={handleClick}>click me</button></div>)
}export default App;
useImperativeHandle
用于向父組件暴露子組件實例的特定函數(shù)。它通常與forwardRef一起使用,在子組件中定義外部可以調(diào)用的實例方法。
- 在子組件中使用forwardRef,將ref作為第二個參數(shù)傳遞給子組件;第三個參數(shù)是依賴項
const ChildComponent = forwardRef((props, ref) => {// 定義需要暴露給父組件的方法useImperativeHandle(ref, () => ({// 這里定義需要暴露給父組件的方法focus: () => {// 在這里編寫具體的邏輯// 注意:這里的 focus 是一個示例,你可以定義任何你需要的方法// 你可以在該方法內(nèi)部執(zhí)行子組件的某個操作// 這個方法會被父組件調(diào)用},// 定義其他的方法...}));// 渲染子組件的 JSXreturn <div>子組件</div>;
});export default ChildComponent;
- 在父組件中創(chuàng)建一個ref對象,并將其傳遞給子組件:
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';const ParentComponent = () => {// 創(chuàng)建 ref 對象const childRef = useRef();// 在父組件中可以通過調(diào)用子組件的方法來訪問子組件的實例const handleClick = () => {childRef.current.focus(); // 調(diào)用子組件的 focus 方法};return (<div><button onClick={handleClick}>調(diào)用子組件方法</button><ChildComponent ref={childRef} /></div>);
};export default ParentComponent;
useContext
允許組件之間通過除了props以外的情況去共享數(shù)據(jù)
如果組件屬性傳遞超過了4層,可以考慮使用上下文
上下文大多數(shù)是用來做全局數(shù)據(jù)管理的
- 創(chuàng)建上下文:
context/themeContext.js
import { createContext } from "react";const ThemeContext = createContext("light")export default ThemeContext
- 給自定義上下文設置值
import { useCallback, useState } from "react";
import ThemeContext from "./context/themeContext";
import TestInput from "./components/TestInput";function App() {const [theme, setTheme] = useState("light")const changeTheme = useCallback(() => {setTheme(prev => {if (prev === 'light') return "dark"return "light"})})return (<div className="wrapper"><ThemeContext.Provider value = {theme}><TestInput /><button onClick={changeTheme}>change theme</button></ThemeContext.Provider></div>)
}export default App;
- 在組件中獲取上下文中的值
import { useContext } from "react"
import ThemeContext from "../../context/themeContext"export default function TestInput(props) {const contextValue = useContext(ThemeContext)return (<div style={{background: contextValue === 'light' ? "#fff" : "#666"}}><input></input></div>)
}