Hooks常用Api总结

334 阅读4分钟

1 useState

  • 使用状态

    const [n, setN] = React.useState(0)
    const [user,setUser] = React.useState({name:'frank'})
    
  • 注意事项1:不可局部更新

    如果是一个对象,不能部分setState,因为setState会帮我们合并属性

    import React, {useState} from "react"
    import ReactDom from "react-dom"
    
    function App() {
        const [user, setUser] = useState({name: 'f', age: 18})
        const onClick = () => {
            setUser({
                ...user, // 把user的所有的属性拷贝过来
                name: 'jack'// 用name: 'jack' 覆盖之前的name
            })
        }
        return (
            <div className="App">
                <h1>{user.name}</h1>
                <h2>{user.age}</h2>
                <button onClick={onClick}>Click</button>
            </div>
        )
    }
    const rootElement = document.getElementById("root")
    ReactDom.render(<App />, rootElement)
    
  • 注意事项:地址要变

    setState(obj)如果obj地址不变,那么React就认为数据没有变化

    const onClick = () => {
            setUser({
            user.name = 'jack'
            setUser(user)
      			//user的地址没变,UI不会更新
        }
    
  • useState接受函数

    好处是减少多余的计算过程(用得少)

    const [user, setUser] = useState(() => ({name: 'frank', age: 18}))
    
  • setState接受函数

    import React, {useState} from "react"
    import ReactDom from "react-dom"
    
    function App() {
        const [n,setN] = useState(0)
        const onClick = () => {
            // setN(n + 1)
            // setN(n + 1)
            // 这样写只会+1,因为setN不会直接改变n的值,要写成函数
    
            setN(i => i +1)
            setN(i => i +1)
        }
        return (
            <div className="App">
                <h1>n : {n}</h1>
                <button onClick={onClick}>+2</button>
            </div>
        )
    }
    
    const rootElement = document.getElementById("root")
    ReactDom.render(<App/>, rootElement)
    

2 useReducer

  • 用来践行Flux/Redux的思想

    四步走

    • 创建初始值initialState
    • 创建所有操作reducer(state, action)
    • 传给useReducer,得到读和写的API
    • 调用写({type : '操作类型'})
  • 例子

    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 * 2}
        } else {
            throw new Error('unknown type')
        }
    }
    
    function App() {
        const [state, dispatch] = useReducer(reducer, initialState)
        const {n} = state
        // 得到一个读API state 和一个写API dispatch
    
        const onClick = () => {
            dispatch({type: 'add', number: 1})
        }
        const onClick2 = () => {
            dispatch({type: 'add', number: 2})
        }
        return (
            <div className="App">
                <h1>n : {n}</h1>
                <button onClick={onClick}>+1</button>
                <button onClick={onClick2}>+2</button>
            </div>
        )
    }
    
    const rootElement = document.getElementById("root")
    ReactDom.render(<App/>, rootElement)
    

3 useContext

  • 上下文(运行一个程序所要知道的所有其他变量)

    • 全局变量是全局的上下文
    • 上下文是局部的全局变量
  • 使用方法

    • 使用C = createContext(initial)创建上下文
    • 使用<C.provider>圈定作用域
    • 在作用域内使用useContext(C)来使用上下文
  • 例子

    import React, {useState, createContext, useContext} from "react"
    import ReactDom from "react-dom"
    
    // 1.
    const C = createContext(null)
    
    function App() {
        const [n, setN] = useState(0)
        return (
            // 2.使用<C.provider>圈定作用域 value的值 读接口和写接口
            // n和setN可以共享给子代的任何组件
            <C.Provider value={{n, setN}}>
                <div className="App">
                    <Farther/>
                </div>
            </C.Provider>
        )
    }
    
    function Farther() {
        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)
    
  • 注意事项

    不是响应式的,你在一个模块将C里面的值改变,另一个模块不会感知到这个变化

4 useEffect

  • 副作用 对环境的改变即为副作用,如修改document.title,但我们不一定非要把副作用放在useEffect里,实际上叫afterRender更好,每次render后调用的函数

  • 用途

    • 作为componentDidMount使用,[]作为第二个参数

    • 作为componentDidUpdate使用,可指定依赖

    • 作为componentWillUnmount使用,通过return

      以上三种用途可同时存在

  • 特点

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

  • 例子

    import React, {useState, useEffect} from "react"
    import ReactDom from "react-dom"
    
    function App() {
        const [n, setN] = useState(0)
        const onClick = () => {
            setN(i => i + 1)
        }
    
        useEffect(() => {
            console.log('第一次渲染后执行这句话')
        }, []) // 里面的变量变化时再次执行 => 不会再次执行
    
        useEffect(() => {
            console.log('每次渲染后执行这句话')
        }) // 任何一个变量变化时都会执行
    
        useEffect(() => {
            if (n !== 0) {
                console.log('n变化了')
            }
        }, [n]) // n变化时执行
        return (
            <div>
                n : {n}
                <button onClick={onClick}>+1</button>
            </div>
        )
    }
    
    const rootElement = document.getElementById("root")
    ReactDom.render(<App/>, rootElement)
    

5 useLayoutEffect

  • 布局副作用

    • useEffect在浏览器渲染完成后执行
    • useLayoutEffect在浏览器渲染前执行
  • 特点

    • useLayoutEffect总是比useEffect先执行
    • useLayoutEffect里的任务最好影响了Layout
  • 经验

    为了用户体验,优先使用useEffect

6 useMemo

  • 例子:多余的render

    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);
        };
    
        return (
            <div className="App">
                <div>
                    <button onClick={onClick}>update n {n}</button>
                </div>
                <Child data={m}/>
                {/* <Child2 data={m}/> */}
            </div>
        );
    }
    
    function Child(props) {
        console.log("child 执行了");
        console.log('假设这里有大量代码')
        return <div>child: {props.data}</div>;
    }
    
    const Child2 = React.memo(Child);
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    效果:点击按钮更新n,子组件中的代码也会执行

    图片.png

  • 将上面例子使用memo改写

    const Child = React.memo(props => {
        console.log("child 执行了");
        console.log('假设这里有大量代码')
        return <div>child: {props.data}</div>;
    })
    
  • 但是react.memo有bug,在添加了监听函数之后,虽然新旧函数功能一样,但是地址不一样,组件重新渲染后函数会生成新的地址

    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 onClickChild = () => {} // 由于APP()重新执行了,所以这句话重新执行,函数的地址变了
    
        return (
            <div className="App">
                <div>
                    <button onClick={onClick}>update n {n}</button>
                </div>
                <Child data={m} onClick={onClickChild}/>
            </div>
        );
    }
    
    const Child = React.memo(props => {
        console.log("child 执行了");
        console.log('假设这里有大量代码')
        return <div onClick={props.onClick}>child: {props.data}</div>;
    })
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App/>, rootElement);
    
  • 用useMemo改写可解决

    const onClickChild = useMemo(() => {
            return () => {
            }
        }, [m])
    
  • 特点

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

    如果你的value是个函数,那么你就要写成

    useMemo(() => (x) => console.log(x))
    

    这是一个返回函数的函数,很难用,于是就有了useCallBack

  • useCallBack

    用法useCallback(x => log(x), [m])等价于

    useMemo(() => x => log(x), [m])

7 useRef

  • 目的

    如果需要一个值,在组件不断render时保持不变

  • 使用

    • 初始化:const count = useRef(0)

    • 读取:count.current

      为什么需要current?为了保证两次useRef是同一个值(只有引用才能做到)

  • forwardRef

    由于props不包含ref,所以需要forwardRef