React Hooks 学习

248 阅读4分钟

React Hooks 中文官网 zh-hans.reactjs.org/docs/hooks-…

React Hooks

内置 Hook描述
useState维护状态
useEffect完成副作用操作
useContext使用共享状态
useReduceruseState 的替代方案,类似 redux
useCallback缓存函数及其引用
useMemo缓存函数及其值
useRef访问 DOM
useImperativeHandle使用子组件暴露的值和方法
useLayoutEffect完成副作用操作,区别于 useEffect,此 hook 会阻塞浏览器绘制

useState

在 React 函数组件上添加内部 state

import React, { useState } from 'react';

export default () => {
  const [count, setCount] = useState(0);
  
  return (
    <>
      <h1>count: {count}</h1>
      {/* 普通更新 */}
      <button onClick={()=>setCount(count + 1)}>add</button>
      {/* 函数式更新 */}
      <button onClick={()=>setCount(prevCount => prevCount + 1)}>add</button>
    </>
  );
};

useEffect

在函数组件中执行副作用操作 如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

import React, { useState, useEffect } from 'react';

export default () => {
  const [count, setCount] = useState(0);
  
  useEffect(()=>{
    console.log('useEffect执行了:componentDidMount');
  }, [])
  
  useEffect(()=>{
    console.log('useEffect执行了:componentDidUpdate');
  }, [count])
  
  useEffect(()=>{
    return ()=>{
      console.log('useEffect执行了:componentWillUnmount');
    }
  }, [])
  
  return (
    <>
      <h1>count: {count}</h1>
      <button onClick={()=>setCount(count + 1)}>add</button>
    </>
  );
};

useContext

Context 提供了一种在组件之间共享值的方式,而不必显式地通过组件树逐层传递 props

import React, { useContext } from 'react';

const data = {
  name: 'falco',
  age: '18'
}

const ComponentContext = React.createContext(data);

const Child = () => {
  const { name, age } = useContext(ComponentContext);
  
  return (
    <div>
      {name} - {age}
    </div>
  )
}

const Parent = () => {
  return (
    <>
      <ComponentContext.Provider value={data}>
        <Child/>
      </ComponentContext.Provider>
    </>
  )
}

export default Parent;

useReducer

是 useState 的替代方案。接收一个形如 (state, action) => newState 的 reducer, 并返回当前的 state 以及与其配套的 dispatch 方法

import React, { useReducer } from 'react';

const initialState = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
]

const reducer = (state, action) => {

  const { type, payload } = action;
  
  switch(type){
    case 'add':
      return [...state, payload];
    case 'update':
      return state.map(item => item.id === payload.id ? { ...item, ...payload } : item);
    case 'remove':
      return state.filter(item => item.id !== payload.id);
    case 'clear':
      return []
    default:
      throw new Error()
  }
}

export default () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <>
      List: {JSON.stringify(state)}
      <div>
        <button onClick={()=>dispatch({type: 'add', payload: { id: 3, name: '王五' }})}>add</button>
        <button onClick={()=>dispatch({type: 'update', payload: { id: 3, name: '赵六' }})}>update</button>
        <button onClick={()=>dispatch({type: 'remove', payload: { id: 1 }})}>remove</button>
        <button onClick={()=>dispatch({type: 'clear', payload: { id: 1 }})}>clear</button>
      </div>
    </>
  )
}

useCallback

useCallback 和 useMemo 都可缓存函数的引用或值,但是从更细的使用角度来说 useCallback 缓存函数的引用,useMemo 缓存计算数据的值

import React, { 
  useState,
  useRef,
  useEffect,
  useCallback
} from 'react';

const usePrevProps = value => {

  const ref = useRef(null)
  
  useEffect(()=>{
    ref.current = value
  }, [])
  
  return ref.current
}

const Button = React.memo(({ title, onClick }) => {
  console.log('render button');
  return <button onClick={onClick}>{title}</button>;
});

export default () => {
  const [count, setCount] = useState(0);
  const [total, setTotal] = useState(0);
  
  // const handleCount = () => setCount(count + 1);
  const handleCount = useCallback(() => setCount(count + 1), [])
  const handleTotal = () => setTotal(total + 1);
  
  const prevHandleCount = usePrevProps(handleCount);  
  
  console.log('两次处理函数是否相等:', prevHandleCount === handleCount); // true
  
  return (
    <div>
      <div>Count is {count}</div>
      <div>Total is {total}</div>
      <br />
      <div>
        <Button title={'add Count'} onClick={handleCount} />
        <Button title={'add Total'} onClick={handleTotal} />
      </div>
    </div>
  )
}

useMemo

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

import React, { 
  useState,
  useRef,
  useEffect,
  useCallback,
  useMemo
} from 'react';

const usePrevProps = value => {
  const ref = useRef(null)
  
  useEffect(()=>{
    ref.current = value
  }, [])
  
  return ref.current
}

export default () => {
  const [count, setCount] = useState(0);
  const [total, setTotal] = useState(0);
  
  // 当count变量值改变的时候才会执行useMemo第一个入参的函数
  const calcValue = useMemo(()=>{
    return Array(100000)
      .fill('')
      .map(v => v);
  }, [count])
  
  const handleCount = () => setCount(count + 1);
  const handleTotal = () => setTotal(total + 1);
  
  const prevCalcValue = usePrevProps(calcValue);  
  console.log('两次计算结果是否相等:', prevCalcValue === calcValue); // false
  
  return (
    <div>
      <div>Count is {count}</div>
      <div>Total is {total}</div>
      <br />
      <div>
        <button onClick={handleCount}>Increment Count</button>
        <button onClick={handleTotal}>Increment Total</button>
      </div>
    </div>
  )
}

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

import React, { useRef } from 'react';

export default () => {
  const inputEl = useRef(null);
  
  const handleFocus = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  }
  
  return (
    <>
      <input ref={inputEl} />
      <button onClick={handleFocus}>Focus</button>
    </>
  )
}

useImperativeHandle

可以让父组件调用到子组件暴露出来的属性/方法。 useImperativeHandle 应当与 forwardRef 一起使用

import React, { 
  useState,
  useRef, 
  forwardRef, 
  useImperativeHandle 
} from 'react';

const Child = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const [value, setValue] = useState(0);
  
  useImperativeHandle(ref, ()=>({
    focus: () => {
      inputRef.current.focus()
    },
    setValue
  }))
  
  return (
    <>
    <div>child-value: {value}</div>
    <input ref={inputRef}/>
    </>
  )
})
export default () => {
  const inputEl = useRef(null);
  
  const handleFocus = () => {
    inputEl.current.focus();
  }
  
  const handleAdd = () => {
    inputEl.current.setValue(val => val + 1)
  }
  
  return (
    <>
      <Child ref={inputEl} />
      <button onClick={handleFocus}>Focus</button>
      <button onClick={handleAdd}>add</button>
    </>
  )
}

useLayoutEffect

在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。 在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新,也就是说它会阻塞浏览器绘制。 所以尽可能使用 useEffect 以避免阻塞视觉更新。

import React, { 
  useState,
  useRef, 
  useEffect, 
  useLayoutEffect 
} from 'react';

const UseEffect = () => {
  const box = useRef(null);
  
  useEffect(()=>{
    box.current.style.backgroundColor = 'green';
  }, [])
  
  return (
    <div
      ref={box}
      style={{
        width: 200,
        height: 200,
        backgroundColor: 'red',
      }}
    >
    UseEffect
    </div>
  )
}
const UseLayoutEffect = () => {

  const box = useRef(null);
  
  useLayoutEffect(()=>{
    box.current.style.backgroundColor = 'green';
  }, [])
  
  return (
    <div
      ref={box}
      style={{
        width: 200,
        height: 200,
        backgroundColor: 'pink',
      }}
    >
    UseLayoutEffect
    </div>
  )
}

export default () => {
  const [visible, setVisible] = useState(false);
  
  return (
    <div className='App'>
      <button onClick={()=>setVisible(!visible)}>挂载/卸载组件</button>
      {
        visible && (
          <div style={{ display: 'flex' }}>
          <UseEffect />
          <UseLayoutEffect />
          </div>
        )
      }
    </div>
  )
}