React学习记录

546 阅读5分钟

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代码中看看使用例子。

附录:

React中文API文档

React在线编写平台