useEffect、useMemo、useCallback的应用和区别:
背景
父组件将一个值传递给子组件,若父组件的其他值发生变化时,子组件也会跟着渲染多次,会造成性能浪费;
简介
useMemo是将父组件传递给子组件的值缓存起来,只有当useMemo中的第二个参数状态变化时,子组件才重新渲染,如果是空数组则只会运行一次;
useCallback是将父组件传递给子组件的方法缓存起来,只有当useCallback中的第二个参数状态变化时,子组件才重新渲染,如果是空数组则只会运行一次;
useMemo
const plusResult = useMemo(() => c + 1 , [c])
性能优化的手段,类似于类组件中的 shouldComponentUpdate来决定是否重新渲染组件。它仅会在某个依赖项改变时才运行回调函数。这种优化有助于避免在每次渲染时都进行高开销的计算。
useCallback
const memorizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memorized 版本(记忆版本),该回调函数仅在某个依赖项改变时才会更新。
useMemo于useCallback区别
不像 useCallback 缓存提供的函数实例,useMemo 调用提供的函数并缓存其结果。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useEffect
useEffect(() => { // dosomething }, [...]/*可选*/);
赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。有点类似于Vue的$nextTick(),可以做一些如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等动作。
和useMemo/useCallback不同的是,useEffect的第二个参数中的值,必须是出现在第一个参数回调中的值,而不能是外面的值。
清除 effect
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。以下就是一个创建订阅的例子:
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅。组件卸载时会调用这个清除函数
subscription.unsubscribe();
};
});
useState和useReducer的应用
useState
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。用法有两种:
setState(newState);
setState(state => state + 1); // 这种可以基于上一次状态来设置一个新值
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
可以看成是useState的替代方案。它接收一个形如(state, action) => newState的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值。并且,类似于Vuex,reducer可以封装一系列改变state的动作(策略模式),比起useState的更改状态方式更加灵活。
以下是用 reducer 重写 useState 一节的计数器示例:
const initialState = {count: 0};
function reducer(state, action) {
// 提醒不要直接改变state.count,而要返回新的状态对象
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<div/>
);
}
Context.Provider、Context.Consumer
背景
在 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但对于某些在程序中很多组件都需要用到的属性数据(如当前认证的用户、主题或首选语言),一层层传递下去显得繁琐无比。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。(有点类似Vue的Inject/Provider)
简介
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context.Provider
官方使用Demo:
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
const ThemeContext = React.createContext();
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 context 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值value。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
// 或者挂载一个 Context 在 class 上的 contextType 属性。上面在类中`static`处已经挂载了
// ThemedButton.contextType = ThemeContext;
描述:当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
Context.Consumer
用于在函数式组件中订阅context
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
本文只介绍最简单、马上可以cv来用的基础用法和介绍,更多分场景用法、注意事项请阅览官方API文档或自行google。
本文介绍到的7种hook,均在此 Demo代码例子 中,看完以上介绍可以到Demo代码中看看使用例子。