关于 React Hooks

110 阅读5分钟

1、React Hooks

  • useState
  • useEffect
  • useContext
  • useReducer
  • useMemo
  • useRef
  • 自定义 Hook

2、useState

  • 使用
const [n,setN] = useState(0)
const [user,setUser] = useState({name:'f'})
  • 注意事项1:不可局部更新
    • setState 不会帮我们合并属性
    • 需要加上 ...state
    • 示例:
function App() {
    const [user,setUser] = useState({name:'Frank', age: 18})
    const onClick = ()=>{
        setUser({
            name: 'Jack'
        })
    }
    return (
        <div className="App">
            <h1>{user.name}</h1>
            <h2>{user.age}</h2>
            <button onClick={onClick}>Click</button>
        </div>
    );
}

//解决方法
    const onClick = ()=>{
        setUser({
            ...user
            name: 'Jack'
        })
    }
  • 注意事项2:地址要变
    • setState(obj) 如果 obj 地址不变,那么 React 不会去查看对象中的内容是否变化,就不会做出改变
    • 示例:
function App() {
    const [user,setUser] = useState({name:'Frank', age: 18})
    const onClick = ()=>{
        user.name = 'jack'
        //React 不会查看对象的内容变化
        setUser(user)
    }
    return (
        <div className="App">
            <h1>{user.name}</h1>
            <h2>{user.age}</h2>
            <button onClick={onClick}>Click</button>
        </div>
    );
}

//解决方法:生成一个新的对象
    const onClick = ()=>{
        setUser({
            ...user
            name: 'Jack'
        })
    }
  • useState 接受函数
    • 写成函数的好处:减少多余计算过程
    • 示例:
const [state, setState] = useState(()=> ({name:'wbh',age:18}) )

//该函数返回初始 state,且只执行一次
  • setState 接受函数(优先使用)
setN(i => i+1)

3、useReducer

  • 用来践行 Flux/Redux 的思想
  • 分四步走
    • 1、创建初始值 initialState
    • 2、创建所有操作 reducer(state,action)
    • 3、传给 useReducer,得到读和写 API
    • 4、调用写 ({type:'操作类型'})
  • 总的来说就是 useState 的复杂版本
  • 示例:
import React, {useReducer } from "react";
import ReactDOM from "react-dom";
const initialState = {
    n:0
}
const reducer = (state, action) => {
    if(action.type === 'add'){
        return {n: state.n + action.number}
    }else if(action.type === 'multi'){
        return {n: state.n + action.number}
    }else{
        throw new Error('unknown type');
    }
}

function App() {
    const [state,dispatch] = useReducer(reducer,initialState)
    const onClick = () => {
        dispatch({type:'add',number:1})
    };
    const onClick2 = () => {
        dispatch({type:'multi',number:2})
    };
    return (
        <div className="App">
            <h1>n:{state.n}</h1>
            <button onClick={onClick}>+1</button>
            <button onClick={onClick2}>*2</button>
        </div>
    );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

4、如何代替 Redux

  • 步骤
      1. 将数据集中在一个 store 对象
      1. 将所有操作集中在 reducer
      1. 创建一个 Context
      1. 创建对数据的读写 API
      1. 将第四步的内容放到第三步的 Context
      1. 用Context.Provider 将 Context 提供给所有组件
      1. 各个组件用 useContext 获取读写 API
  • 也就是 useReducer 和 useContext 的结合

5、useContext

  • 上下文
    • 全局变量是全局的上下文
    • 上下文是局部的全局变量
  • 使用方法
    • 1、使用 C = createContext(null) 创建上下文
    • 2、使用 <C.Provider> 圈定作用域
    • 3、在作用域内使用 useContext(C) 来使用上下文
  • 示例:
const C = createContext(null)

function App() {
    const [n,setN] = useState(0)
    return (
        <C.Provider value={{n:n,setN:setN}}>
            <div>
                我是爷爷:
                <Baba />
            </div>
        </C.Provider>
    )
}
function Baba(){
    return (
        <div>
            我是爸爸,
            <Child />
        </div>
    )
}
function Child(){
    const {n,setN} = useContext(C)
    const onClick = () =>{
        setN(i => i+1)
    }
    return (
        <div>
            我是儿子,n:{n}
            <button onClick={onClick}>+1</button>
        </div>
    )
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 注意:useContext 不是响应式的,是一个 Child 组件变化后,逐级通知,然后重新渲染 App 的过程

6、useEffect

  • 副作用

    • 对环境的改变即为副作用,如修改 document.title
    • 实际上叫做 afterRender 更好,每次 render 后运行
  • 用途

    • 作为 componentDidMount 使用,[] 作为第二个参数
    • 作为 componentDidUpdate ,可指定依赖
    • 作为 componentWillUnmount 使用,通过return
  • 以上三种用途可同时存在

  • 特点

    • 如果同时存在多个 useEffect,会按照出现次序执行

7、useLayoutEffect

  • 布局副作用
    • useEffect 在浏览器渲染完成后执行
    • useLayoutEffect 在浏览器渲染前执行
  • 特点:
    • useLayoutEffect 总是比 useEffect 先执行
    • useLayoutEffect 里的任务最好影响了输出外观
  • 建议:
    • 为了用户体验,优先使用 useEffect

8、useMemo

  • React.memo
    • React 默认会出现多余的 render,当一个App重新渲染时,其中的子组件也会重新渲染
    • 那么,如果 props 不变,就没有必要再次执行一个函数组件,使用 React.memo
    • 示例:
import React from "react";
import ReactDOM from "react-dom";

function App() {
    const [n, setN] = React.useState(0);
    const [m, setM] = React.useState(0);
    const onClick = () => {
        setN(n + 1);
    };
    const onClick2 = () => {
        setM(m => m + 1);
    };
    return (
        <div className="App">
            <div>
                <button onClick={onClick}>update n {n}</button>
                <button onClick={onClick2}>update m {m}</button>
            </div>
            <Child data={m}/>
        </div>
    );
}

const Child = React.memo( props => {
    console.log("child 执行了");
    console.log('假设这里有大量代码')
    return <div>child: {props.data}</div>;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 但是 React.memo 存在 bug
    • 添加监听函数后,直接失效
    • 因为 App 运行时会再次生成新的 onClickChild
    • 新旧函数虽然功能一样,但是地址不一样
    • 示例:
function App() {
    ...
    const onClickChild = () => {}
    return (
        ...
        <Child data={m} onClick={onClick3}/>
    );
}
  • 解决方法:使用 useMemo 来调用对应的监听事件
const onClickChild = useMemo(()=>{
    return ()=>{
        ...
    }
},[m])
//这样就只会在 m 改变时,重新生成 onClickChild 函数
  • useMemo 特点

    • 第一个参数是 () => value
    • 第二个参数是依赖 [m,n]
    • 只有当依赖变化时,才会计算出新的 value
    • 依赖不变时,就会重用之前的 value
  • 注意

    • 如果 value 是一个函数,那么就需要写成 useMemo(() => {return ()=>{...} },[n])
    • 这是一个返回函数的函数
    • 于是出现了 useCallback
  • useCallback

    • 用法:
useCallback(()=>{...} ,[n])
//等价于,省略了第一个 () => 
useMemo(() => {return ()=>{...}},[n])

9、useRef

  • 目的
    • 如果需要一个值,在组件不断 render 的过程中保持不变
    • 初始化:const count = useRef(0)
    • 读取:count.current
    • 为什么需要 current
    • 为了保证组件渲染后的 count.current 是同一个值(只有引用能做到)
  • 注意:在 React 中,count 变化后并不会渲染组件,只能通过 setState 的方法来渲染

10、forwardRef

  • 示例:
import React, { useRef } from "react";
import ReactDOM from "react-dom";

function App() {
    const buttonRef = useRef(null);
    return (
        <div className="App">
            <Button3 ref={buttonRef}>按钮</Button3>
        </div>
    );
}
const Button3 = React.forwardRef((props, ref) => {
    return <button className="red" ref={ref} {...props} />;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 由于 props 不包含 ref ,所以需要 forwardRef
  • 但是,大部分时候不需要 ref