1、useEffect
作用:处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class Component的componentDidMount、componentDidUpdate、componentWillUnmount等生命周期
- 无参数:
不传递第二个参数可以模拟componentDidMount、componentDidUpdate,注意和传递第二个参数的区别,任何修改都调用useEffect更新。
useEffect(()=>{
// ...do something
})
- 传递空数组
可以模拟componentDidMount
useEffect(()=>{
// ...do something
}, [])
- 传递第二个参数
可以模拟componentDidMount和componentDidUpdate
useEffect(()=>{
console.log(count);
}, [count]) //count更新执行
- return
可以模拟componentDidUpdate
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id); // 在组件销毁的时候执行
}, []);
2、useContext
用一个简单的示例来做辅助理解。
import React, { createContext, useContext, useState } from "react";
const initialState = { m: 100, n: 50 }; // 定义初始state
const Store = createContext({}); // 创建Context
export default function App() {
const [state, setState] = useState(initialState); // 创建state读写接口
return (
<Store.Provider value={{ state, setState }}> // value值传递变量。
<Father></Father>
</Store.Provider>
);
}
const Father = (props) => {
console.log("Father");
const { state, setState } = useContext(Store); //拿到 名字为Store的上下文的value,用两个变量来接收读写接口
const addN = () => {
setState((state) => {
return { ...state, n: state.n + 1 };
});
};
const addM = () => {
setState((state) => {
return { ...state, m: state.m + 1 };
});
};
return (
<div>
爸爸组件
<div>n:{state.n}</div>
<Child />
<button onClick={addN}>设置n</button>
<button onClick={addM}>设置m</button>
</div>
);
};
const Child = (props) => {
console.log("son");
const { state } = useContext(Store); // 读取state
return (
<div>
儿子组件
<div>m:{state.m}</div>
</div>
);
};
3、useReducer
useState的替代方案,useReducer只是一个小范围内的状态管理工具。在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
useReducer的使用场景:
1、如果你的state是一个数组或者对象
2、如果你的state变化很复杂,经常一个操作需要修改很多state
3、如果你希望构建自动化测试用例来保证程序的稳定性
4、如果你需要在深层子组件里面去修改一些状态(关于这点我们下篇文章会详细介绍)
5、如果你用应用程序比较大,希望UI和业务能够分开维护
import React, { useReducer, useEffect } from "react";
import ReactDOM from "react-dom";
const initialState = {
count: 0,
step: 1
};
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: "tick" });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return (
<>
<h1>{count}</h1>
<input
value={step}
onChange={(e) => {
dispatch({
type: "step",
step: Number(e.target.value)
});
}}
/>
</>
);
}
function reducer(state, action) {
const { count, step } = state;
if (action.type === "tick") {
return { count: count + step, step };
} else if (action.type === "step") {
return { count, step: action.step };
} else {
throw new Error();
}
}
4、useMemo 和 useCallback
useMemo 和 useCallback他们可以用来缓存函数、组件、变量,以避免两次渲染间的重复计算。
为什么使用 useMemo 和 useCallback
使用 memo 通常有三个原因: 1. ✅ 防止不必要的 effect。 2. ❗️防止不必要的 re-render。 3. ❗️防止不必要的重复计算。后两种优化往往被误用,导致出现大量的无效优化或冗余优化。
(1)防止不必要的 effect
当变量直接或者通过依赖链成为 useEffect 的依赖项时,那它可能需要被缓存。这是 useMemo 和 useCallback 最基本的用法。
// useMemo示例
const Component = () => {
// 在 re-renders 之间缓存 a 的引用
const a = useMemo(() => ({ test: 1 }), []);
useEffect(() => {
// 只有当 a 的值变化时,这里才会被触发
doSomething();
}, [a]);
// the rest of the code
};
// useCallback 示例
const Component = () => {
// 在 re-renders 之间缓存 fetch 函数
const fetch = useCallback(() => {
console.log('fetch some data here');
}, []);
useEffect(() => {
// 仅fetch函数的值被改变时,这里才会被触发
fetch();
}, [fetch]);
// the rest of the code
};
(2)防止不必要的 re-render
- 组件什么时候会 re-render
三种情况组件会re-render: 1. 当本身的 props 或 state 改变时。 2. Context value 改变时,使用该值的组件会 re-render。 3. 当父组件重新渲染时,它所有的子组件都会 re-render,形成一条 re-render 链。
存在的问题:第三个种 re-render 时机经常被开发者忽视,导致代码中存在大量的无效缓存。
- 组件什么时候会 re-render
const App = () => {
const [state, setState] = useState(1);
const onClick = useCallback(() => {
console.log('Do something on click');
}, []);
return (
// 无论 onClick 是否被缓存,Page 都会 re-render
<Page onClick={onClick} />
);
};
当使用 setState 改变 state 时,App 会 re-render,作为子组件的 Page 也会跟着 re-render。这里 useCallback 是完全无效的,它并不能阻止 Page 的 re-render。
- 2.如何防止子组件 re-render
必须同时缓存 onClick 和组件本身,才能实现 Page 不触发 re-render
const PageMemoized = React.memo(Page);
const App = () => {
const [state, setState] = useState(1);
const onClick = useCallback(() => {
console.log('Do something on click');
}, []);
return (
// useCallback结合memo一起才能控制子组件无效re-render
<PageMemoized onClick={onClick} />
);
};
注意下面这种情况,由于 value 会随着 App 的 re-render 重新定义,引用值发生变化,导致 PageMemoized 仍然会触发 re-render。
const PageMemoized = memo(({value, onClick}) => {
console.log(value); // 触发父组件的handleBtn方法时,仍然会打印value值,去掉value是常亮活着是memoized类型数据时,触发handleBtn不会打印value
return <div onClick={onClick}>value的长度:{value?.length}</div>
});
const App = () => {
const [state, setState] = useState(1);
const onClick = useCallback(() => {
console.log('Do something on click');
}, []);
const handleBtn = () => {
setState(state + 1);
}
return (
<>
<div onClick={handleBtn}>点击按钮</div>
<span>{state}</span>
// PageMemoized还是会re-render,因为value不是一个memoized, 使用useMemo处理useMemo(() => [1, 2, 3], [])后,handleBtn触发时子组件console里不会再打印。
<PageMemoized onClick={onClick} value={[1, 2, 3]} />
</>
);
};
现在可以得出结论,必须同时满足以下两个条件,子组件才不会 re-render: 1. 子组件自身被缓存。 2. 子组件所有的 prop 都是memoized。
(3)防止不必要的重复计算
useMemo 的基本作用是,缓存计算结果,避免在每次渲染时都进行高开销的计算。
实际上,组件渲染才是性能的瓶颈,应该把 useMemo 用在程序里渲染昂贵的组件上,而不是数值计算上。当然,除非这个计算真的很昂贵,比如阶乘计算。
至于为什么不给所有的组件都使用 useMemo,useMemo 是有成本的,它会增加整体程序初始化的耗时,并不适合全局全面使用,它更适合做局部的优化。
function App(props) {
const start = props.start;
const list = props.list;
const fibValue = useMemo(() => fibonacci(start), [start]); // 缓存耗时操作
// const MemoList = useMemo(() => <List list={list} />, [list]);
return (
<>
<div>Do some expensive calculation: {fibValue}</div>
{MemoList}
<Other />
</>
);
}
// 只有列表项改变时组件才会re-render
const MemoList = React.memo(({ list }) => {
return (
<ul>
{list.map(item => (
<li key={item.id}>{item.content}</li>
))}
</ul>
);
});
相比React.memo,useMemo在组件内部调用,可以访问组件的props和state,所以它拥有更细粒度的依赖控制。
5、useRef
- 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
- 返回的 ref 对象在组件的整个生命周期内保持不变
- 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
- 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
- useRef 类似于类组件的 this。
tips: 欢迎各位指正!
参考:
1.How to useMemo and useCallback: you can remove most of them
2.如何正确使用 useMemo 和 useCallback
3.A Complete Guide to useEffect
4.React Hooks完全上手指南