浅谈 React Hooks(二)

栏目: 服务器 · 发布时间: 5年前

内容简介:在上一篇文章中,我们谈到 Hooks 给 React 带来的一些在开发体验上的改变,如果你已经开始尝试 React Hooks,也许你会跟我一样碰到一个令人疑惑的地方,如果没有的话,那就再好不过啦,我就权当做个记录,以便他人之需。我们先以官方的例子开始:看到

在上一篇文章中,我们谈到 Hooks 给 React 带来的一些在开发体验上的改变,如果你已经开始尝试 React Hooks,也许你会跟我一样碰到一个令人疑惑的地方,如果没有的话,那就再好不过啦,我就权当做个记录,以便他人之需。

如何绑定事件?

我们先以官方的例子开始:

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

看到 onClick 绑定的那个匿名函数了吗?这样写的话,每次 render 的时候都会重新生成一个新的函数。这在之前可能不需要太在意,因为我们一般只是拿 Function Component 来实现一些展示型组件,在其之下不会有太多的子组件。但是如果我们拥抱 Hooks 之后,那么就不可控了。

虽然说在一般情况下,这并不会造成太大的性能问题,而且 Function Component 本身的性能就要比 Class Component 更好一点,但是难免会碰到需要优化的时候,比方说在重构原来的 Class Component 的时候,其中有个子组件是个 PureComponent ,便会使子组件的这个优化失效 ,那么怎么解决呢?

使用 useCallbackuseMemo 来保存函数的引用,避免重复生成新的函数

function Counter() {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => {
    setCount(count => count + 1)
  }, []);
  
  // 或者用useMemo
  // const handleClick = useMemo(() => () => {setCount(count => count + 1)}, []);
   
  return (
    <div>
      <p>count: {count}</p>
	  {/* Child为PureComponent */}
      <Child callback={handleClick} />
    </div>
  )
}
复制代码

可见 useCallback(fn, inputs) 等同于 useMemo(() => fn, inputs) ,那么这两个 Hook 具体是怎么做到的呢?我们可以从源码中一窥究竟,我们以 useCallback 为例( useMemo 大体上都是一样的,就返回值不同,后面会提到)。

首先,在第一次执行 useCallback 时,React内部会调用 ReactFiberHooks 中的 mountCallback ,之后再次执行时调用的都是 updateCallback ,具体代码可以看这里: github.com/facebook/re…

我们一点点来看,先看下 mountCallback :

function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
复制代码

发现核心在于 mountWorkInProgressHook 这个方法

function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    queue: null,
    baseUpdate: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    firstWorkInProgressHook = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
复制代码

代码比较简单,就不一一解释了,从上面的代码我们可以得知 Hooks 的本体:

const hook = {
  memoizedState: null,
  baseState: null,
  queue: null,
  baseUpdate: null,
  next: null,
}
复制代码

我们主要关注 memoizedStatenextmemoizedState 在不同的 Hook 中存放的值会有所不同,在 useCallback 中存的就是入参的值 [callback, deps]next 的值就是下一个 hook,也就是说 Hooks 其实就是一个单向链表,这也就解释了为什么 Hooks 需要在顶层调用,不能在循环、条件语句、嵌套函数中使用,因为需要保证每次调用的顺序一致。

再来看之后的 updateCallback :

function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
  // 这个hook就是第一次mount的hook
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  // 所以这里的memoizedState就是mount时候存着的[callback, deps]
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      // 比较两次的deps,相同的话就直接返回之前存的callback,而不是新传进来的callback
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  hook.memoizedState = [callback, nextDeps];
  return callback;
}
复制代码

useMemo 的实现与 useCallback 类似,大概看一下:

function mountMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  // 与useCallback不同的地方就是memoizedState中存的是nextCreate执行之后的结果
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
    
  // 返回执行结果
  return nextValue;
}

function updateMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps: Array<mixed> | null = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  
  // 这里也一样,存的是nextCreate执行之后的结果
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  
    // 返回执行结果
  return nextValue;
}
复制代码

由以上代码便可以看出 useCallbackuseMemo 在用法上的区别了。

除了这两个方法以外,还可以通过 context 来传递由 useReducer 生成的 dispatch 方法,来避免直接传递 callback ,因为 dispatch 是不变的。这个方法跟前面两种有本质上的区别,它从源头上就阻止了callback的传递,所以也就不会有前面提到的性能方面的顾虑,这也是官方推荐的方法,特别是组件树很大的情况下。所以上面的代码如果通过这种方式来写的话,就会是下面这样,有点像 Redux

import React, { useReducer, useContext } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    default:
      throw new Error();
  }
}

const TodosDispatch = React.createContext(null);

function Counter() {
  const [state, dispatch] = useReducer(reducer, {count: 0});

  return (
    <div>
      <p>count: {state.count}</p>
      <TodosDispatch.Provider value={dispatch}>
        <Child />
      </TodosDispatch.Provider>
    </div>
  )
}

function Child() {
  const dispatch = useContext(TodosDispatch);
  return (
    <button onClick={() => dispatch({type: 'increment'})}>
      click
    </button>
  )
}
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Collective Intelligence

Programming Collective Intelligence

Toby Segaran / O'Reilly Media / 2007-8-26 / USD 39.99

Want to tap the power behind search rankings, product recommendations, social bookmarking, and online matchmaking? This fascinating book demonstrates how you can build Web 2.0 applications to mine the......一起来看看 《Programming Collective Intelligence》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具