10 mins 带你入门 React Hooks

356 阅读4分钟

Why Hooks

  1. Function Component 可以使用状态
  2. 简化 Class Component 的生命周期
  3. 无需为 this 指向问题伤破脑经
  4. 解决 render props & HOC 嵌套缺陷
  5. 官方爸爸让你学

How to use Hooks

现在开始我们用创建一个计数器的方式开始学习使用React Hooks

useState

useState 的用法很简单,传入一个初始 state,返回一个 state 以及修改 state 的函数。

// useState 返回的 state 是个常量
// 每次组件重新渲染之后,当前 state 和之前的 state 都不相同
// 即使这个 state 是个对象
const [count, setCount] = useState(1)

setCount 就相当于 setState ,可以传入一个新的状态或者函数。例如:

setCount(2)
setCount(prevCount => prevCount + 1)

其实 useState 内部可以简单理解为(只是理解):

// 闭包存储 state 数据
function useState(initialState){
    let state = initialState;
    function setState = (newState, action) => {
        state = newState;
    }
    return [state, setState]
}

那接下来编写 Count 组件:

function Counter() {
  const [count, setCount] = React.useState(0)
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}

useEffect

现在我们有了新的需求: 在 count 每次更新的时候打印 count 的值,这时我们需要用 useEffect 来实现

function Counter() {
  const [count, setCount] = React.useState(0)
  
  React.useEffect(() => {
    console.log(count)
  })
  
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
}

以上在每次重新渲染时, useEffect 就可以实现打印,可以把它当作是 componentDidUpdatecomponentDidMount, 同于 Class Component中的

componentDidMount() {
  console.log(count)
}
componentDidUpdate() {
  console.log(count)
}

另外它可以返回一个函数,用于解绑一些副作用,功能类似于 componentWillUnmount

React.useEffect(() => {
    console.log(count)
    // 注意: 当我们每次更新计数时,都会先打印 clean 这行 log
    return () => console.log('clean', count)
})

为了防止我们不必要的渲染,useEffect 加入第二个参数, 只有在 count 改变的时候才会执行。

React.useEffect(() => {
    console.log(count)
    // 当我们每次更新计数时,都会先打印 clean 这行 log
    return () => console.log('clean', count)
}, [count])

useReducer

useReduceruseState 的替代方案,主要是为了解决多个 state 子值的问题。(会Redux的一看就会明白)

看一下🌰:

// 初始化 state
const initialState = {count: 0};

// 类于 redux 的 reducer
function reducer(state, action) {
  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}
      /* dispatch 调用 action 操作 */
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </div>
  );
}

useMemo

这是个用于优化的 hook, 返回一个 memoized 值。说白了就是会利用闭包的特性存储上次的参数,如果和这次传入的参数一致就返回之前的结果。这叫做记忆化技术。

下面我们来看个反🌰:

function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');

    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }

    return <div>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

这个函数式组件面临的问题就是 expensive 方法不管是 count 还是 val 改变时都会由于组件的重新渲染, 重新计算。

但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算:

// count 如果不变就不会再次执行
const expensive = React.useMemo(() => {
    console.log('compute');
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
        sum += i;
    }
    return sum;
}, [count]);

useCallback

useCallbackuseMemo 很相似,不过它返回的是函数。下面继续看个🌰:

function Parent() {
    const [count, setCount] = React.useState(1);
    const [val, setVal] = React.useState('');

    const callback = React.useCallback(() => {
        return count;
    }, [count]);
    
    return <div>
        <h4>{count}</h4>
        // callback 作为 props 传入
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

function Child({ callback }) {
    const [count, setCount] = React.useState(() => callback());
    
    React.useEffect(() => {
        setCount(callback());
    }, [callback]);
    
    return (
        <div>
            {count}
        </div>
    )
}

现在就是有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

自定义 Hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

自定义 Hook 是一个函数,其名称以 use 开头,函数内部可以调用其他的 Hook

function useConsoleCount(count) {
  React.useEffect(() => {
    console.log(count)
    // 当我们每次更新计数时,都会先打印 clean 这行 log
    return () => console.log('clean', count)
  }, [count])
}

function Counter() {
  const [count, setCount] = React.useState(0);
  
  useConsoleCount(count)
  
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
}

结尾

  • 这篇只是简单介绍hook的使用,有错误的地方欢迎指正
  • 关于其他的 hook 可直接阅读官方文档