react hooks

351 阅读8分钟

Conext

  • context可以实现某些值在context上下文之间传递
    import React, { Component, createContext } from 'react';
    import './App.css';
    
    class Bottom extends Component {
        render() {
            return (
            <MyContext.Consumer>
                {
                    (value) => <h1>{value.initValue}</h1>
                }
            </MyContext.Consumer>
            )
        }
    }
    
    class Middle extends Component {
        render() {
            return <Bottom/>
        }
    }
    
    const MyContext = createContext();
    
    function App() {
        return (
            <MyContext.Provider value={{initValue: 1}}>
                <Middle/>
            </MyContext.Provider>
        );
    }
    
    export default App;
    
    
  • 如果存在多个context也是可以的
    import React, { Component, createContext } from 'react';
    import './App.css';
    
    class Bottom extends Component {
        render() {
            return (
            <MyContext.Consumer>
                {
                    (value) => (
                        <NextContext.Consumer>
                            {
                                (nextValue)=> <h1>value: {value.initValue}, nextValue: {nextValue.nextInitValue}</h1>
                            }
                        </NextContext.Consumer>
                    )
                }
            </MyContext.Consumer>
            )
        }
    }
    
    class Middle extends Component {
        render() {
            return <Bottom/>
        }
    }
    
    const MyContext = createContext();
    const NextContext = createContext();
    
    function App() {
        return (
            <MyContext.Provider value={{initValue: 1}}>
                <NextContext.Provider value={{nextInitValue: 2}}>
                    <Middle/>
                </NextContext.Provider>
            </MyContext.Provider>
        );
    }
    
    export default App;
    
    

contextType

  • 之前我们必须使用Customer才能共享Provider中的变量,这样使得我们的代码变得不优雅,contextType就是解决这个问题的
  • contextType只能用到类组件并且只能有一个context
    import React, { Component, createContext } from 'react';
    import './App.css';
    
    const MyContext = createContext();
    
    class Bottom extends Component {
        // 声明
        static contextType = MyContext;
        render() {
            const value = this.context;
            return (
                <h1>{value.initValue}</h1>
            )
        }
    }
    
    class Middle extends Component {
        render() {
            return <Bottom/>
        }
    }
    
    
    function App() {
        return (
            <MyContext.Provider value={{initValue: 1}}>
                <Middle/>
            </MyContext.Provider>
        );
    }
    
    export default App;
    
    

lazy,Suspense和ErrorBoundary

  • lazy加载的代码webpack会做拆分单独打包为一个文件,Suspense的fallback需要指定在lazy文件未加载之前显示的组件

    import React, { Component, lazy, Suspense } from 'react';
    import './App.css';
    
    const LazyCom = lazy(()=>import('./lazyCom.jsx'))
    class App extends Component {
        state = {
            error: true
        }
    
        componentDidCatch() {
            this.setState({
                error: true
            })
        }
    
        render() {
            const { error } = this.state;
            if(error) {
                return <div>error</div>
            }
            return <div>
                <Suspense fallback={<div>loading</div>}>
                    <LazyCom/>
                </Suspense>
            </div>
        }
    }
    export default App;
    
    
  • 如果我们不写Suspense但是使用了lazy会抛出异常,界面什么也不显示,所以我们需要做错误边界

    import React, { Component, lazy, Suspense } from 'react';
    import './App.css';
    
    const LazyCom = lazy(()=>import('./lazyCom.jsx'))
    class App extends Component {
        state = {
            error: true
        }
        
        // 类似于componentDidCatch,用来将异常传递给state
        // static getDerivedStateFromError() {
        //     return {
        //        hasError: true
        //    }
        // }
    
        componentDidCatch() {
            this.setState({
                error: true
            })
        }
    
        render() {
            const { error } = this.state;
            if(error) {
                return <div>error</div>
            }
            return <div>
                <LazyCom/>
            </div>
        }
    }
    export default App;
    
    

memo

  • 传统的react组件中,父组件渲染一定会渲染自组件,所以我们需要重写父组件的shouldComponentUpdate或者使用PureComponent

    class Test extends PureComponent {
        render() {
            console.info("render");
            return null;
        }
    }
    
    class App extends Component {
        state = {
            count: 0,
        }
    
        onClick = () => {
            const { count } = this.state;
            this.setState({
                count: count + 1
            });
        }
    
        render() {
            console.info("red")
            return <div>
                <button onClick={this.onClick}>加1</button>
                <Test name="text"/>
            </div>
        }
    }
    export default App;
    
    
  • 如果Test是函数组件,我们就可以使用mome,所以memo的作用和 PureComponent是一样的

    
    const Test = memo((props) => {
            console.info("render");
            return <h1>{props.name}</h1>;
    })
    
    class App extends Component {
        state = {
            count: 0,
            
        }
    
        onClick = () => {
            const {  count } = this.state;
            this.setState({
                count: count + 1
            });
        }
    
        render() {
            console.info("red")
            return <div>
                <button onClick={this.onClick}>加1</button>
                <Test name={"test"} />
            </div>
        }
    }
    export default App;
    
    

useState

  • useState可以为函数组件设置状态,传入初始化的值

    
    class App1 extends Component {
        state = {
            count: 0,
        }
    
        onClick = () => {
            const {  count } = this.state;
            this.setState({
                count: count + 1
            });
        }
    
        render() {
            const { count } = this.state;
            return <div>
                <button onClick={this.onClick}>加1</button>
                <h1>{count}</h1>
            </div>
        }
    }
    
    function App() {
        const [count, setCount] = useState(0);
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
                <h1>{count}</h1>
        </div>
    }
    export default App;
    
    
  • 我们以前经常在constructor做一些初始化的值的计算 然后把props作为自己的state,useState可以接受函数 计算初始值,并且只会执行一次

    function App(props) {
        const [count, setCount] = useState(() => {
            return props.defaultValue || 0
        });
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
                <h1>{count}</h1>
        </div>
    }
    // 也可以使用对象
    function App(props) {
        const [info, setInfo] = useState(() => {
            return props.defaultValue || { age: 1, name: 'test'}
        });
        return <div>
            <button onClick={() => {setInfo(oldState => ({ ...oldState, age: info.age + 1 }))}}>加1</button>
            <h1>{info.age}{info.name}</h1>
        </div>
    }
    
  • useState内部也做了浅判断不会引起重复渲染

    // 不会重复渲染
    function App(props) {
        const [count, setCount] = useState(0);
        return <div>
            <button onClick={() => {setCount(count)}}>加1</button>
            <h1>{count}</h1>
        </div>
    }
    // 会重复渲染
    function App(props) {
        const [info, setInfo] = useState(() => {
            return props.defaultValue || { age: 1, name: 'test'}
        });
        return <div>
            <button onClick={() => {setInfo(oldState => ({ ...oldState }))}}>加1</button>
            <h1>{info.age}{info.name}</h1>
        </div>
    }
    

useEffect

  • 如果使用原始的class编写监听窗口变化的组件

    class App1 extends Component {
        state = {
            count: 0,
            size: {
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight,
            }
        }
    
        onResize = () => {
            this.setState({
                size: {
                    width: document.documentElement.clientWidth,
                    height: document.documentElement.clientHeight,
                }
            })
        }
        componentDidMount() {
            document.title = this.state.count;
            window.addEventListener('resize', this.onResize, false);
        }
    
        componentDidUpdate() {
            document.title = this.state.count;
        }
    
        componentWillUnmount() {
            window.removeEventListener('resize', this.onResize, false)
        }
    
        onClick = () => {
            const {  count } = this.state;
            this.setState({
                count: count + 1
            });
        }
    
        render() {
            const { count, size } = this.state;
            return <div>
                <button onClick={this.onClick}>加1</button>
                <h1>{count}</h1>
                <h2>size{JSON.stringify(size)}</h2>
            </div>
        }
    }
    
  • useEffect使得我们可以抽离之前生命周期的一些函数,用来代替componentDidMount,componentDidUpdate,componentWillUnMount,是在渲染之后执行的

  • useEffect第二个参数是数组,useEffect通过浅比较数组中的参数来决定是否执行

    
    function App(props) {
        const [count, setCount] = useState(0);
        
        const [size, setSize] = useState({
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
        });
    
        const onResize = () => {
            setSize({
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight,
            });
        }
        // 第二个参数是空 每次都会渲染,相当于componentDidMount,componentDidUpdate
        useEffect(() => {
            document.title = count;
        });
        
        // 第二个参数是空数组,每次比较都一样所以只会执行一次
        useEffect(()=> {
            window.addEventListener('resize', onResize, false);
            // 返回的参数会在的时候组件卸载的时候执行,类似于componentWillUnMount
            return () => {
                window.removeEventListener('resize', onResize, false);
            }
        }, []);
    
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            <h2>size{JSON.stringify(size)}</h2>
        </div>
    }
    

useContext

  • useContext类似于contextType,但是避免了contextType只能使用一个context的问题

    const myContext = createContext();
    
    function Test() {
        const count = useContext(myContext)
        return (
            <h1>{count}</h1>
        )
    }
    
    function App(props) {
        const [count, setCount] = useState(0);
    
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            <myContext.Provider value={count}>
                <Test/>
            </myContext.Provider>
        </div>
    }
    export default App;
    

useMemo/useCallback

  • useMemo是在渲染之前执行的,不同于useEffect,里面是不能写副作用的,类似于shouldComponentUpdate,但是返回值并不能控制是否渲染

  • useMemo主要是用来做优化的,下面的代码因为每次渲染onClick的引用会改变,导致Test一直渲染


    const Test = memo(function(props) {
        console.info('render Test')
        return (
            <h1>{props.count}</h1>
        )
    });
    
    function App() {
        const [count, setCount] = useState(0);
    
        // 第二个参数决定useMemo是否渲染,如果不传是没意义的,每次都会执行
        // 没办法做优化
        const double = useMemo(() => {
            return count * 2
        }, [count === 1]);
    
        const onClick = () => {
            console.info('click');
        }
    
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            <h1>{double}</h1>
            <Test count={double} onClick={onClick}/>
        </div>
    }
    export default App;
  • 使用useMemo可以防止引用的变化,useCallBack跟useMemo效果一样的,只是少一层函数的嵌套

    
    function App() {
        // setConut 的引用每次渲染都不会变
        const [count, setCount] = useState(0);
        const double = useMemo(() => {
            return count * 2
        }, [count === 1]);
    
        const onClick = useMemo(() => {
            return () => {
                console.info('click');
            }
        }, []);
    
        // 也可以在里面改变State.但是需要防止死循环
        useCallback(() => {
            setCount((count2) => count2 + 1)
        },[]);
       
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            <h1>{double}</h1>
            <Test count={double} onClick={onClick}/>
        </div>
    }
    export default App;
    
    

useRef

  • useRef可以拿到类组件的ref然后执行类组件的方法

    
    class Test extends PureComponent {
        select() {
            console.info('select')
        }   
    
        render() {
            const { count, onClick } = this.props;
            return (
                <h1 onClick={onClick}>{count}</h1>
            )
        }
    }
    
    function App() {
        // setConut 的引用每次渲染都不会变
        const [count, setCount] = useState(0);
        // testRef的引用不会每次都变化
        const testRef = useRef();
       
        const onClick = useMemo(() => {
            return () => {
                testRef.current.select();
            }
        }, [testRef]);
    
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            <Test ref={testRef} count={count} onClick={onClick}/>
        </div>
    }
    export default App;
    
    
  • useRef可以存储一些变量,这些变量不会产生新的引用,类似于this,例如下面的例子会导致定时器无法清除,因为time变化了

    
    function App() {
        // setConut 的引用每次渲染都不会变
        const [count, setCount] = useState(0);
        // testRef的引用不会每次都变化
        let time;
       
        useEffect(() => {
            time = setInterval(() => {
                setCount(count => count + 1)
            }, 1000);
        }, []);
    
        useEffect(() => {
           if(count >= 10) {
               clearInterval(time);
           }
        });
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
        </div>
    }
    export default App;
    
  • 使用useRef修复上面的问题

    
    function App() {
        // setConut 的引用每次渲染都不会变
        const [count, setCount] = useState(0);
        // testRef的引用不会每次都变化
        let time = useRef();
       
        useEffect(() => {
            time.current = setInterval(() => {
                setCount(count => count + 1)
            }, 1000);
        }, []);
    
        useEffect(() => {
           if(count >= 10) {
               clearInterval(time.current);
           }
        });
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
        </div>
    }
    export default App;
    

自定义hooks

  • 自定义hooks函数可以复用组件的项目逻辑

    
    function useCount(defaultCount) {
         // setConut 的引用每次渲染都不会变
         const [count, setCount] = useState(0);
         // testRef的引用不会每次都变化
         let time = useRef();
        
         useEffect(() => {
             time.current = setInterval(() => {
                 setCount(count => count + 1)
             }, 1000);
         }, []);
     
         useEffect(() => {
            if(count >= 10) {
                clearInterval(time.current);
            }
         });
         return [count, setCount];
    }
    
    function useSize() {
        const [size, setSize] = useState({
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
        });
    
        const onResize = () => {
            setSize({
                width: document.documentElement.clientWidth,
                height: document.documentElement.clientHeight,
            });
        }
        
        // 第二个参数是空数组,每次比较都一样所以只会执行一次
        useEffect(()=> {
            window.addEventListener('resize', onResize, false);
            // 返回的参数会在的时候组件卸载的时候执行,类似于componentWillUnMount
            return () => {
                window.removeEventListener('resize', onResize, false);
            }
        }, []);
    
        return size;
    }
    
    function App() {
        const [count, setCount] = useCount(0);
        const size = useSize();
        return <div>
            <button onClick={() => {setCount(count + 1)}}>加1</button>
            <h1>{count}</h1>
            {JSON.stringify(size)}
        </div>
    }