zoukankan      html  css  js  c++  java
  • React Hooks

    1) what is Hooks?

    之前也有函数式组件,但是没有状态,无法保存数据,所以一直用类式组件

    class MyCount extends Component {
        state = {
            count: 0,
        }
    
        componentDidMount() {
            this.interval = setInterval(() => {
                this.setState({ count: this.state.count + 1 })
            }, 1000)
        }
    
        componentWillUnmount() {
            if (this.interval) {
                clearInterval(this.interval)
            }
        }
    
        render() {
            return <span>{this.state.count}</span>
        }
    }
    
    • 引入Hooks函数,重写上述组件
    function MyCountFunc() {
        const [count, setCount] = useState(0)
    
        useEffect(() => {
            const interval = setInterval(() => {
                setCount(x => x + 1)
            }, 1000)
    
            return () => clearInterval(interval)
        })
    
        return <span>{count}</span>
    }
    
    1. setCount代替之前 this.setState的功能,修改state数据,其实也是reducer的功能,useState也是useReducer实现的
    2. useEffect实现 componentDidMount 的功能;return 的回调函数实现 componentWillUnmount 的功能

    2) State Hooks

    1. useState -- 值类型,每次传入的都是新的值
    function MyCountFunc() {
        const [count, setCount] = useState(0)
    
        // setCount两种用法
        // setCount(value)
        // setCount(callback)    
    
        useEffect(() => {
            const interval = setInterval(() => {
                // setCount(count + 1)  值固定为1,不会变化
                setCount(x => x + 1)
            }, 1000)
    
            return () => clearInterval(interval)
        }, [])
    
        return <span>{count}</span>
    }
    
    1. useReducer -- 引用类型

    如果state是一个对象,每次要求传递的state是一个新的对象,而且这个对象比较复杂,就不能像useState中的函数那样来修改,否则可能实现不了修改的目的(setCount(count + 1));就像redux中 Object.assign()、JSON.parse(JSON.stringify())

    function countReducer(state, action) {
        switch(action.type) {
            case 'add':
                return state + 1
            case 'minus':
                return state - 1
            default:
                return state
        }
    }
    
    function MyCountFunc() {
        const [count, dispatchCount] = useReducer(countReducer, 0)
    
        useEffect(() => {
            const interval = setInterval(() => {
                dispatchCount({ type: 'minus' })
            }, 1000)
    
            return () => clearInterval(interval)
        }, [])
    
        return <span>{count}</span>
    }
    

    3) Effect Hook

    function MyCountFunc() {
        const [count, dispatchCount] = useReducer(countReducer, 0)
        const [name, setName] = useState('firm')
    
        /*
         * 不添加 dependencies,每次组件内的 state(这里就是count、name)变化,都会update component
         *      1. 所以第一次Mount Component,会输出 'effect invoked'
         *      2. 之后每次Update Component,就会先执行上一次状态中useEffect的return 回调函数      =>      'effect deteched'   =>      再执行这一次状态中的useEffect   =>  'effect invoked'
         *      3. 切换组件时,Unmount Component,就只有    => 'effect deteched'
         */
        useEffect(() => {
            console.log('effect invoked')
    
            return () => console.log('effect deteched')
        }, [])
    
        return (
            <div>
                <input value={name} onChange={e => setName(e.target.value)} />
                <button onClick={() => dispatchCount({ type: 'add' })}>{count}</button>
            </div>
        )
    }
    
    
    /*
     * useEffect和useLayoutEffect
     * 1. 在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用
     * 2. 可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。     它会在所有的 DOM 变更之后同步调用 effect。
     *  所以,组件挂载时,Layout effect invoked     =>   effect invoked
     *       组件更新时, Layouteffect deteched、Layouteffect invoked    =>      effect deteched、effect invoked
     *       组件卸载时,   effect deteched    =>   Layouteffect deteched
     */
    useEffect(() => {
        console.log('effect invoked')
    
        return () => console.log('effect deteched')
    }, [count])
    
    useLayoutEffect(() => {
        console.log('Layout effect invoked')
    
        return () => console.log('Layout effect deteched')
    }, [count])
    

    3) context Hook

    • Context设计的目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题(黑夜模式)或首选语言(简体中文、英语...)。
    1. /lib下新建my-context.js
    import React from 'react'
    
    export default React.createContext('')
    //=> 创建一个Context对象,初始值为''
    //=> 当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值
    
    1. _app文件下
    render() {
        const { Component, pageProps } = this.props
    
        return (
            <Container>
                <Layout>
                    <MyContext.Provider value={this.state.context}>
                        <Component {...pageProps} />
                        <button onClick={() => this.setState({ context: `${this.state.context}111`})}>update context</button>
                    </MyContext.Provider>
                </Layout>
            </Container>
        )
    }
    
    1. 组件b
    const context = useContext(MyContext)
        
    return (
        <div>
            <input value={name} onChange={e => setName(e.target.value)} />
            <button onClick={() => dispatchCount({ type: 'add' })}>{count}</button>
            <p>{context}</p>
        </div>
    )
    

    4) Ref Hook

    • 类式组件中ref的使用,获取DOM元素的节点
    class MyCount extends Component {
        constructor() {
            super();
            // 创建一个 ref 来存储 spanRef 的 DOM 元素
            this.spanRef = React.createRef()
        }
        
        state = {
            count: 0,
        }
    
        componentDidMount() {
            // React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值
            this.refs.current
            this.interval = setInterval(() => {
                this.setState({ count: this.state.count + 1 })
            }, 1000)
        }
    
        componentWillUnmount() {
            if (this.interval) {
                clearInterval(this.interval)
            }
        }
    
        render() {
            // 告诉 React 我们想把 <span> ref 关联到构造器里创建的 `spanRef` 上
            return <span ref={this.spanRef}>{this.state.count}</span>
        }
    }
    
    • 无状态组件引入ref,有了useRef就可以存储ref数据了
    function MyCountFunc() {
        // ....
        const inputRef = useRef()
    
        useEffect(() => {
            // ...
            console.log(inputRef)
    
            // return
        }, [])
    
        return (
            <div>
                <input ref={inputRef} value={name} onChange={e => setName(e.target.value)} />
                {/* ... */}
            </div>
        )
    }
    

    5) Hooks渲染优化

    function MyCountFunc() {
        const [count, dispatchCount] = useReducer(countReducer, 0)
        const [name, setName] = useState('firm')
    
        const config = {
            text: `conut is ${count}`,
            color: count > 3 ? 'red' : 'blue',
        }
    
        return (
            <div>
                <input value={name} onChange={e => setName(e.target.value)} />
                <Child 
                    config={config}
                    onButtonClick={() => dispatch({ type: 'add' })}
                />
            </div>
        )
    }
    
    function Child({ onButtonClick, config }) {
        console.log('child render');
        return (
            <button onClick={onButtonClick} style={{ color: config.color }}>
                {config.text}
            </button>
        )
    }
    

    • 问题:当前组件,无论是状态中的count还是name发生变化,都会打印出'child render' ,为什么会这样?

    Child是一个无状态组件,是否重新渲染是看传给它的props(即onButtonClick和config)是否变化

    • 解决:使用memo对Child组件进行优化一下

    • React.memo

    React.memo是一个高阶组件。
    如果你的组件给定了一样的props得到同样的渲染结果,可以调用React.memo()将其包起来通过依赖项提升某些方面的性能。这就意味着React将跳过重新渲染过程,复用上次的渲染结果。
    React.memo只影响props的改变。如果你包裹在memo中的组件有用到useState或者useContext,那么当state或者context变化是,组件仍将重新渲染。
    默认情况下,它只会对props对象中的复杂对象进行浅层比较。若想要控制整个比较过程,可以自定义比较函数作为第二个参数传入。

    • 注: memo的功能和类式组件中的shouldComponentUpdate()函数很像,但是比较函数在props相等时返会true,不等时返回false。

    const Child = memo(function Child({ onButtonClick, config }) {
        console.log('child render');
        return (
            <button onClick={onButtonClick} style={{ color: config.color }}>
                {config.text}
            </button>
        )
    })
    

    再次测试,结果仍然是,无论count或者name变化时都输出'child props',why?

    1. count变化时,props中的config变化,Child组件中的color属性和text都变化,必然导致重新渲染,可为什么name变化,也会重新渲染Child?
    2. name是MyCountFunc组件的state,输入框中的name变化 => 触发onChange事件,setName函数执行 => name值改变,MyCountFunc组件会重新渲染 => MyCountFunc函数重新执行 => 形成一个新的函数闭包 => 形成与之对应的新的config对象(新的堆内存) => Child子组件的props发生变化 => Child组件重新渲染

    所以,props还是变了。但要想不重新渲染,onButtonClick和config不能改变。如何实现呢?


    1. useMemo
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
    

    返回一个备忘值。
    传入一个"创建"备忘值的函数,useMemo只会在依赖项变化时重新计算备忘值。这个优化能够避免渲染时的高昂开销。如果不提供依赖项,每次渲染都会得到一个新值。

    在渲染是,传给useMemo的函数会执行,所以不要再渲染时做一些一般不做的事情,避免带来side effect,那是useEffect做的事情,要区分开。

    useMemo是用来做性能优化的,不是作为semantic guarantee.在将来,React可能会忘记一些之前备忘的值并在下一次渲染时重新计算它们,例如为屏幕外的组件释放内存。写出的代码应该在没有useMemo时也能工作,然后用useMemo优化性能。

    注意

    依赖项数组并不是作为参数传给了数组。不过从概念上说,它们表示的是这意思:函数中引用的值也应出现在依赖项数组中。以后,编译器会足够高级,自动创建依赖项数组。

    我们推荐在eslint-plugin-react-hooks包下使用exhaustive-deps规则。它会在不正确的指定依赖项时发出警告并建议修复。


    const config = useMemo(() => ({
        text: `count is ${count}`,
        color: count > 3 ? 'red' : 'blue',
    }), [count])
    

    此时,再检测,点击按钮时,count变化 => 'child props';但是,输入框中改变name时,还会输出'child props',说明MyCountFunc重新渲染了,why?

    onButtonClick传入的是箭头函数,而箭头函数的this是由它所定义的词法作用域决定,所以MyCountFunc重新渲染时,会生成一个新的MyCountFunc执行上下文,不同于之前的MyCountFunc,其中包含的箭头函数由于词法作用域不同,当然不同于之前的箭头函数,所以props的OnButtonClick还是改变了,必然会重新渲染。
    下一步就是要优化箭头函数,使之


    1. useCallback
    const memorizedCallback = useCallback(
        () => {
            doSomething(a, b)
        },
    [a, b])
    

    返回一个备忘的回调函数。

    传入内联回调和依赖项数组。useCallback返回一个只在依赖项改变时才改变的备忘版回调。这在将回调传递给依靠引用相等(两个变量引用完全相等的对象)来避免不必要渲染(例如shouldComponentUpdate)的优化子组件时非常有用。

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

    Note
    依赖项数组并不是作为参数传给了数组。不过从概念上说,它们表示的是这意思:函数中引用的值也应出现在依赖项数组中。以后,编译器会足够高级,自动创建依赖项数组。
    我们推荐在eslint-plugin-react-hooks包下使用exhaustive-deps规则。它会在不正确的指定依赖项时发出警告并建议修复。


    function MyCountFunc() {
        const [count, dispatchCount] = useReducer(countReducer, 0)
        const [name, setName] = useState('firm')
    
        const config = useMemo(() => ({
            text: `count is ${count}`,
            color: count > 3 ? 'red' : 'blue',
        }), [count])
    
        const buttonClick = useCallback(() => {
            dispatchCount({ type: 'add' })
        }, [])
    
        return (
            <div>
                <input value={name} onChange={e => setName(e.target.value)} />
                <Child 
                    config={config}
                    onButtonClick={buttonClick}
                />
            </div>
        )
    }
    

    优化后的结果

  • 相关阅读:
    人脸识别数据库
    美赛
    排序算法
    个人作业——软件工程实践总结作业
    事后诸葛亮(团队)
    个人作业——软件产品案例分析
    Alpha冲刺总结
    Alpha冲刺——Day2
    Alpha冲刺——Day1
    I Know Alpha冲刺随笔集
  • 原文地址:https://www.cnblogs.com/wydumn/p/12209068.html
Copyright © 2011-2022 走看看