zoukankan      html  css  js  c++  java
  • 浅谈 Hooks

    如果你很熟悉 vue 与 react ,兴许你也觉得 vue3.0 抄袭了react,这项react 在不久前发布的新技术,在 vue3.0 中被重新搬上了舞台。也使它重新活跃在了人们的视野中,我技术不深,与大家分享我的见解和猜测。
    在这里插入图片描述


    useState
    使用状态

    const [n, setN] = React.useState(0)
    const [user, setUser] = React.useState({name: 'Jack', age: 18})
    
    • 1
    • 2

    注意事项:
    无局部更新能力

    如果state是一个对象,能否部分setState?
    答案是不行,因为setState不会帮我们合并属性
    那么useReducer会合并属性吗?也不会!
    因为React认为这应该是你自己要做的事情

    function App(){
        const [user, setUser] = React.useState({name: 'Jack', age: 18})
        const onClick = () =>{
            //setUser不可以局部更新,如果只改变其中一个,那么整个数据都会被覆盖
            // setUser({
            //  name: 'Frank'
            // })
            setUser({
                ...user, //拷贝之前的所有属性
                name: 'Frank' //这里的name覆盖之前的name
            })
        }
        return (
            <div className='App'>
                <h1>{user.name}</h1>
                <h2>{user.age}</h2>
                <button onClick={onClick}>Click</button>
            </div>
        )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意事项:
    地址要进行改变

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

    useState接受函数

    const [state, setState] = useState(() => {return initialState})
    
    该函数返回初始state,且只执行一次
    
    • 1
    • 2
    • 3

    在这里插入图片描述



    setState接受函数

    setN(i => i + 1)
    
    如果你能接受这种形式,应该优先使用这种形式
    
    • 1
    • 2
    • 3

    在这里插入图片描述


    useReducer

    用来践行Flux/Redux思想

    一、创建初始值initialState
    
    二、创建所有操作reducer(state, action);
    
    三、传给userReducer,得到读和写API
    
    四、调用写({type: '操作类型'})
    
    总的来说,useReducer 是 useState 的复杂版
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述


    如何代替 Redux

    一、将数据集中在一个 store 对象
    
    二、将所有操作集中在 reducer
    
    三、创建一个 Context
    
    四、创建对数据的读取 API
    
    五、将第四步的内容放到第三步的 Context
    
    六、用 Context.Provider 将 Context 提供给所有组件
    
    七、各个组件用 useContext 获取读写API
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    import React, { useReducer, useContext, useEffect } from "react";
    import ReactDOM from "react-dom";
    
    const store = {
        user: null,
        books: null,
        movies: null
    };
    
    function reducer(state, action) {
        switch (action.type) {
            case "setUser":
                return { ...state, user: action.user };
            case "setBooks":
                return { ...state, books: action.books };
            case "setMovies":
                return { ...state, movies: action.movies };
            default:
                throw new Error();
        }
    }
    
    const Context = React.createContext(null);
    
    function App() {
        const [state, dispatch] = useReducer(reducer, store);
    
        const api = { state, dispatch };
        return (
            <Context.Provider value={api}>
                <User />
                <hr />
                <Books />
                <Movies />
            </Context.Provider>
        );
    }
    
    function User() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/user").then(user => {
                dispatch({ type: "setUser", user: user });
            });
        }, []);
        return (
            <div>
                <h1>个人信息</h1>
                <div>name: {state.user ? state.user.name : ""}</div>
            </div>
        );
    }
    
    function Books() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/books").then(books => {
                dispatch({ type: "setBooks", books: books });
            });
        }, []);
        return (
            <div>
                <h1>我的书籍</h1>
                <ol>
                    {state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
                </ol>
            </div>
        );
    }
    
    function Movies() {
        const { state, dispatch } = useContext(Context);
        useEffect(() => {
            ajax("/movies").then(movies => {
                dispatch({ type: "setMovies", movies: movies });
            });
        }, []);
        return (
            <div>
                <h1>我的电影</h1>
                <ol>
                    {state.movies
                        ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>)
                        : "加载中"}
                </ol>
            </div>
        );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    
    // 帮助函数
    
    // 假 ajax
    // 两秒钟后,根据 path 返回一个对象,必定成功不会失败
    function ajax(path) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (path === "/user") {
                    resolve({
                        id: 1,
                        name: "Frank"
                    });
                } else if (path === "/books") {
                    resolve([
                        {
                            id: 1,
                            name: "JavaScript 高级程序设计"
                        },
                        {
                            id: 2,
                            name: "JavaScript 精粹"
                        }
                    ]);
                } else if (path === "/movies") {
                    resolve([
                        {
                            id: 1,
                            name: "爱在黎明破晓前"
                        },
                        {
                            id: 2,
                            name: "恋恋笔记本"
                        }
                    ]);
                }
            }, 2000);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    useContext

    全局变量是全局的上下文
    上下文是局部的全局变量

    
    
        一、使用 C = createContext(initial) 创建上下文
    
        二、使用 <C.Provider> 圈定作用域
    
        三、在作用域内使用 useContext(C)来使用上下文
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述


    useEffect
    副作用 (API 名字叫得不好)

    
        对环境的改变即为副作用,如修改 document.title
    
        但我们不一定非要把副作用放在 useEffect 里面
    
        实际上叫做 afterRender 更好,每次render后执行
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    用途

    一、作为 componentDidMount 使用,[ ] 作第二个参数
    
    二、作为 componentDidUpdate 使用,可指定依赖
    
    三、作为 componentWillUnmount 使用,通过 return
    
    四、以上三种用途可同时存在
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述


    特点

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

    useLayoutEffect
    布局副作用

        useEffect 在浏览器渲染完成后执行
        useLayoutEffect 在浏览器渲染前执行
    
    • 1
    • 2
    function App1() {
        const [n, setN] = useState(0)
        const time = useRef(null)
        const onClick = () => {
            setN(i => i + 1)
            time.current = performance.now()
        }
        useLayoutEffect(() => {
            if (time.current) {
                console.log(performance.now() - time.current) //大概是0.7ms
            }
        })
        useEffect(() => {
            if (time.current) {
                console.log(performance.now() - time.current) //大概是2.7ms
            }
        })
        return (
            <div className="App">
                <h1>n: {n}</h1>
                <button onClick={onClick}>Click</button>
            </div>
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述



    特点

        useLayoutEffect 总比 useEffect 先执行
        useLayoutEffect 里的任务最好影响了 Layout
    
    • 1
    • 2
    /* useLayoutEffect比useEffect先执行 */
    function App2() {
        const [n, setN] = useState(0)
        const onClick = () => {
            setN(i => i + 1)
        }
        //执行顺序打印出 2、3、1
        useEffect(() => {
            console.log(1)
        })
        useLayoutEffect(() => {
            console.log(2)
        })
        useLayoutEffect(() => {
            console.log(3)
        })
        return (
            <div className="App">
                <h1>n: {n}</h1>
                <button onClick={onClick}>Click</button>
            </div>
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    经验

    为了用户体验,优先使用 useEffect (优先渲染)

    useMemo

    要理解 React.useMemo

        需要先讲 React.memo
        React默认有多余的render
    
    • 1
    • 2
    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会重新执行Child组件*/}
                    <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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
        将代码中的 Child 用React.memo(Child) 代替
        如果 props 不变,就没有必要再次执行一个函数组件
        最终代码:
    
    • 1
    • 2
    • 3
    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会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                </div>
                <Child data={m}/>
            </div>
        );
    }
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div>child: {props.data}</div>;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    但是

        这玩意有一个bug
        添加了监听函数之后,一秒破功因为 App 运行时,会再次执行 onClickChild,生成新的函数
        新旧函数虽然功能一样,但是地址引用不一样!
    
    • 1
    • 2
    • 3
    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
        const onClickChild = () => {}
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                </div>
                {/*但是如果传了一个引用,则React.memo无效。因为引用是不相等的*/}
                <Child data={m} onClick={onClickChild}/>
            </div>
        );
    }
    //使用React.memo可以解决重新执行Child组件的问题
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div onClick={props.onClick}>child: {props.data}</div>;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    怎么办? 用useMemo:

    function App() {
        const [n, setN] = React.useState(0);
        const [m, setM] = React.useState(0);
        const onClick = () => {
            setN(n + 1);
        };
        const onClick1 = () => {
            setM(m + 1);
        };
        const onClickChild = () => {}
        const onClickChild1 = useMemo(() => {
            return () => {
                console.log(`on click child m: ${m}`)
            }
        }, [m])
        return (
            <div className="App">
                <div>
                    {/*点击button会重新执行Child组件*/}
                    <button onClick={onClick}>update n {n}</button>
                    <button onClick={onClick1}>update m {m}</button>
                </div>
                {/*但是如果传了一个引用,则React.memo无效。因为引用是不相等的*/}
                {/*<Child data={m} onClick={onClickChild}/>*/}
                {/*onClickChild1使用useMemo可以消除此bug*/}
                <Child data={m} onClick={onClickChild1}/>
            </div>
        );
    }
    //使用React.memo可以解决重新执行Child组件的问题
    const Child = React.memo(props => {
            console.log("child 执行了");
            console.log('假设这里有大量代码')
            return <div onClick={props.onClick}>child: {props.data}</div>;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    useMemo
    特点

    第一个参数是 () => value
    
    第二个参数是依赖 [m, n]
    
    只有当依赖变化时,才会计算出新的 value
    
    如果依赖不变,那么就重用之前的 value
    
    这不就是 Vue 2的 computed 吗?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意

        如果你的 value 是一个函数,那么你就要写成useMemo(() => x => console.log(x))
        这是一个返回函数的函数
        是不是很难用?于是就有了useCallback
    
    • 1
    • 2
    • 3

    useCallback
    用法

    useCallback(x => console.log(x), [m]) 等价于
    useMemo( () => x => console.log(x), [m])
    
    • 1
    • 2

    forwardRef
    useRef

    可以用来引用 DOM 对象
    也可以用来引用普通对象
    
    • 1
    • 2

    forwardRef

    props 无法传递 ref 属性

    function App(){
        const buttonRef = useRef(null)
        return (
            <div>
                <Button ref={buttonRef}>按钮</Button>
                {/* 控制台报错:
                        Warning: Function components cannot be given refs.
                      Attempts to access this ref will fail.
                      Did you mean to use React.forwardRef()?
                  */}
            </div>
        )
    }
    
    const Button = (props) => {
        console.log(props) // {ref: undefined, children: "按钮"}
        return <button {...props} />
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    实现 ref 的传递:由于 props 不包含 ref,所以需要 forwardRef

    import React, {forwardRef, useRef} from 'react';
    
    function App(){
        const buttonRef = useRef(null)
        return (
            <div>
                <Button ref={buttonRef}>按钮</Button2>
            </div>
        )
    }
    const Button = forwardRef((props, ref) => {
        console.log(ref)  //可以拿到ref对button的引用,forwardRef仅限于函数组件,class 组件是默认可以使用 ref 的
        return <button ref={ref} {...props} />;
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    自定义 Hook
    封装数据操作

    // useList.js
    import {useState, useEffect} from 'react'
    
    const useList = () => {
        const [list, setList] = useState(null)
        useEffect(() => {
            ajax().then(list => {
                setList(list)
            })
        }, []) //确保只在第一次运行
        return {
            list,
            setList
        }
    }
    export default useList
    
    function ajax(){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([
                    {id: 1, name: 'Frank'},
                    {id: 2, name: 'Jack'},
                    {id: 3, name: 'Alice'},
                    {id: 4, name: 'Bob'},
                    {id: 5, name: 'Han'}
                ])
            }, 1000)
        })
    }
    
    //index.js
    import useList from './hooks/useList'
    
    function App(){
        const {list, setList} = useList()
        return (
            <div>
                <h1>List</h1>
                {
                    list ? (
                        <ol>
                            {
                                list.map(item => {
                                    return <li key={item.id}>{item.name}</li>
                                })
                            }
                        </ol>
                    ):(
                        '加载中...'
                    )
                }
            </div>
        )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述



    再比如下面这个

    // useList.js
    import {useState, useEffect} from 'react'
    
    const useList = () => {
        const [list, setList] = useState(null)
        useEffect(() => {
            ajax().then(list => {
                setList(list)
            })
        }, []) //确保只在第一次运行
        return {
            list,
            addItem: name => {
                setList([...list, {id: Math.random(), name}])
            },
            deleteIndex: index => {
                setList(list.slice(0, index).concat(list.slice(index + 1)))
            }
        }
    }
    export default useList
    
    function ajax(){
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([
                    {id: 1, name: 'Frank'},
                    {id: 2, name: 'Jack'},
                    {id: 3, name: 'Alice'},
                    {id: 4, name: 'Bob'},
                    {id: 5, name: 'Han'}
                ])
            }, 1000)
        })
    }
    
    //index.js
    import useList from './hooks/useList'
    
    function App() {
        const {list, deleteIndex} = useList()
        return (
            <div>
                <h1>List</h1>
                {
                    list ? (
                        <ol>
                            {
                                list.map((item,index) => {
                                    return (
                                        <li key={item.id}>
                                            {item.name}
                                            <button
                                                onClick={() => {
                                                    deleteIndex(index);
                                                }}
                                            >
                                                x
                                            </button>
                                        </li>
                                    )
                                })
                            }
                        </ol>
                    ) : (
                        '加载中...'
                    )
                }
            </div>
        )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    在这里插入图片描述


    你还可以在自定义 Hook 里使用 Context
    useState 只说了不能在 if 里,没说不能在函数里运行,只要这个函数在函数组件里运行即可
    自定义 Hook 完全可以代替 Redux
  • 相关阅读:
    hdoj_1556Color the ball
    wchar_t与char转换(总结)
    算法艺术——网络最大流
    poj_3268Silver Cow Party
    poj_2352Stars
    BellmanFord模板
    saas模式
    什么是管道
    什么是CMMI
    saas模式
  • 原文地址:https://www.cnblogs.com/5118svip/p/13799889.html
Copyright © 2011-2022 走看看