性能优化
1.防止浪费渲染
memo
useMemo
useCallback
误区:props的更新其实不会导致组件的重新渲染,往往其原因是其父组件重新呈现带来的props改变
什么是浪费渲染?就是实际上没有产生任何新的dom但是却进行了有关的所有计算
在不需要因为状态更新重新渲染的部分因为组件内的状态更新而重新渲染就可以使用优化
在children子道具传入的时候,由于子道具是先于父组件创建的,所以不会受父组件的影响
eg:
function SlowComponent() {
// If this is too slow on your maching, reduce the `length`
const words = Array.from({ length: 100_000 }, () => "WORD");
return (
<ul>
{words.map((word, i) => (
<li key={i}>
{i}: {word}
</li>
))}
</ul>
);
}
function Counter({children}) {
const [count, setCount] = useState(0);
return (
<div>
<h1>Slow counter?!?</h1>
<button onClick={() => setCount((c) => c + 1)}>Increase: {count}</button>
{children}
</div>
);
}
export default function Test() {
return (
<Counter>
<SlowComponent />
</Counter>
);
}
2.确保流畅
有关记忆化:
在执行纯函数的时候使用,因为纯函数在相同参数的时候其结果一定是相同的,所以只需要把缓存中的数据拿出来就够了
常规Memo失效的情况:在传入一个对象作为参数的时候常规Memo会失效,因为其父组件重新渲染后,传入的对象也会重新分配成一个新对象哪怕它完全和之前一样,所以这个时候相当于传入的对象改变了,就会导致 整个组件的重新渲染,
Memo:对于一个静态的部分,为了防止每次父组件渲染的时候都重新渲染这个部分,所以可以使用memo来进行优化防止每次都要重新渲染这部分
useMemo:
基本作用:
useMemo 用于记忆化计算值
它会缓存计算结果,只有当依赖项改变时才重新计算
避免在每次渲染时都进行昂贵的计算或创建新的对象
性能优化考虑:
不是所有值都需要 useMemo
使用 useMemo 本身也有开销
主要用于以下情况:
计算过程复杂
计算结果是对象/数组(防止引用变化)
计算结果被用于其他 Hook 的依赖项
计算结果传递给使用 memo 的子组件
useCallback:
记忆化函数:
useCallback 会记忆化(memoize)这个函数,确保除非依赖项发生变化,否则每次重新渲染时返回的都是同一个函数引用。
避免不必要的重渲染:
当父组件重新渲染时,如果不使用 useCallback,每次都会创建一个新的函数引用。
对于使用 memo 的子组件来说,即使函数逻辑完全相同,由于引用变化也会导致重新渲染。
有时候一个函数的调用会依赖函数本身,每次重新创建函数的时候地址改变导致react认为其是一个新函数从而会产生循环调用问题
解决方案:用usecallback让这个函数在非必要的时候不重新渲染即可
eg:
const getCity = useCallback(async function getCity(id){
try {
dispatch({type: "loading"});
const res = await fetch(`${BASE_URL}/cities/${id}`);
const data = await res.json();
dispatch({type: "city/loaded", payload: data});
} catch (err) {
dispatch({type: "rejected", payload: err.message});
}
},
useEffect(() => {
getCity(id);
}, [id, getCity]);[currentCity.id]);
useTransiton
3.有限使用和导入
代码拆分延迟加载,减少第三方库导入
懒加载:
有些时候,在我们打包一个js代码的时候,一次性打包一堆会导致用户端的负载过大造成一些问题,所以我们可以把这些js代码分批打包,在需要调用它们的时候再进行服务端到客户端的发送是实现更好的调用,一般来说,懒加载在路由层面的使用相对多一点。
EG:
const Homepage=lazy(()=>import("./pages/Homepage"));
Redux
Redux是一个第三方库,用于管理全局状态,它是一个完全独立的库可以集成进任何一个框架之中。Redux与上下文API+useReducer很相似。
什么时候使用Redux?
有许多需要经常更新的全局UI状态的时候,对于全局远程状态往往使用React Query之类的库完成。
Redux的基本原理:
创建了一个仓库用来储存状态和管理状态的方法(这里的方法必须是纯函数)
从动作的创建者开始,分派由动作创建者产生的动作,这届动作将到达store然后在那里由正确的reducer来处理它来更新状态出发新的渲染。
中间件
可以用于专门的做一些数据请求的操作,因为redux的reducer中需要纯函数,而组件中又要尽可能保重数据的清洁性,所以中间件此时基本是一个位于调度和存储之间的函数,所以那些有副作用的函数往往在中间件中。
现代redux使用
现代使用的redux一般是使用RTK来进行使用,使用的时候用createSlice函数创建一个新的仓库,输入name属性以后对仓库进行初始化然后写一个reducers:用于放置处理状态的函数。
出现多个状态需要共同使用的时候,最好使用prepare对其进行一个预处理,把状态打包到同一个payload里面使用。
eg:
const accountSlice = createSlice({
name: "account",
initialState,
reducers: {
deposit(state, action) {
state.balance += action.payload;
},
withdraw(state, action) {
state.balance -= action.payload;
},
requestLoan: {
prepare(amount, purpose) {
return {
payload: { amount, purpose },
};
},
reducer(state, action) {
state.loan = action.payload.amount;
state.loanPurpose = action.payload.purpose;
state.balance = state.balance + action.payload.amount;
}
},
payLoan(state) {
const amount = state.loan;
state.balance = state.balance - amount;
state.loan = 0;
state.loanPurpose = "";
},
isLoading(state, action) {
state.isLoading = action.payload;
},
convertCurrency(state,action) {
state.isLoading = true;
state.balance = state.balance * action.payload;
state.isLoading = false;
}
},
});
与上下文api的对比
上下文API会直接集成在react中但是redux需要额外配置,redux的初步配置会比较麻烦但是一旦配置好后续增加状态片更加的简单,而且可以避免一些提供者地狱问题,看起来更加的简洁。
在需要进行一些异步操作的时候,redux可以使用中间件来完成而上下文API没有类似的内置方法。