zoukankan      html  css  js  c++  java
  • React 新特性学习

    1 context

    2 contextType

    3 lazy

    4 suspense

    5 memo

    6 hooks

    7 effect hooks

    ===========

    1 Context

      提供了一种方式,能够让数据在组件树中传递而不必一级一级手动传递 (但是不要滥用,因为它会破坏组件的复用性)

    API: createContext(defaultValue)

    示例1:基本用法

    import React,{ Component,createContext } from 'react';//在这里导出context函数
    import logo from './logo.svg';
    import './App.css';
    
    const BatteryContext = createContext();//在这里创建context
    
    //孙子组件
    class Leaf extends Component {
        render(){
            //在这里定义 consumer 消费者
            return (
                <BatteryContext.Consumer>
                {
                    Battery => <h1>BatteryName:{Battery}</h1>
                }
                </BatteryContext.Consumer>
            )
        }
    }
    
    //子组件
    class Middle extends Component{
        render(){
            return <Leaf/>;
        }
    }
    
    //父组件
    class App extends Component {
        render(){
            //在这里定义 Provider context的提供者
            return (
                <BatteryContext.Provider value = {60}>
                    <Middle/>
                </BatteryContext.Provider>
            )
        }
    }
    export default App;

     示例2: 动态改变其值:

    import React,{ Component,createContext } from 'react';//在这里导出context函数
    import logo from './logo.svg';
    import './App.css';
    
    const BatteryContext = createContext();//在这里创建context
    
    //孙子组件
    class Leaf extends Component {
        render(){
            //在这里定义 consumer 消费者
            return (
                <BatteryContext.Consumer>
                {
                    Battery => <h1>BatteryName:{Battery}</h1>
                }
                </BatteryContext.Consumer>
            )
        }
    }
    
    //子组件
    class Middle extends Component{
        render(){
            return <Leaf/>;
        }
    }
    
    //父组件
    class App extends Component {
        state = {
            battery:50
        }
        render(){
            const {battery} = state;
            //在这里定义 Provider context的提供者
            return (
                <BatteryContext.Provider value = {battery}>
                    <button type="button" 
                        onclick={()=>{
                            this.setState({
                                battery: battery-1
                            })
                        }}/>
                    <Middle/>
                </BatteryContext.Provider>
            )
        }
    }
    export default App;

    示例3:多个context嵌套

    import React,{ Component,createContext } from 'react';//在这里导出context函数
    import logo from './logo.svg';
    import './App.css';
    
    const BatteryContext = createContext();//在这里创建context
    const OnlineContext = createContext();//在这里创建context
    
    //孙子组件
    class Leaf extends Component {
        render(){
            //在这里定义 consumer 消费者
            return (
                <BatteryContext.Consumer>
                {
                    battery => (
                        <OnlineContext.Consumer>
                        {
                            online => <h1>battery:{battery},online:{online}</h1>
                        }
                        </OnlineContext.Consumer>
                    )
                }
                </BatteryContext.Consumer>
            )
        }
    }
    
    //子组件
    class Middle extends Component{
        render(){
            return <Leaf/>;
        }
    }
    
    //父组件
    class App extends Component {
        state = {
            battery:50,
            online:false,
        }
        render(){
            const {battery,online} = state;
            //在这里定义 Provider context的提供者
            return (
                <BatteryContext.Provider value = {battery}>
                <OnlineContext.Provider value = {online}>
                    <button type="button" 
                        onclick={()=>{
                            this.setState({
                                battery: battery-1
                            })
                        }}>
                        </button>
                    <button type="button" 
                        onclick={()=>{
                            this.setState({
                                online: !online
                            })
                    }}/>
                    </button>
                    <Middle/>
                </OnlineContext.Provider>
                </BatteryContext.Provider>
    
            )
        }
    }
    export default App;

    示例4:使用static 化简

    import React,{ Component,createContext } from 'react';//在这里导出context函数
    import logo from './logo.svg';
    import './App.css';
    
    const BatteryContext = createContext();//在这里创建context
    const OnlineContext = createContext();//在这里创建context
    
    //孙子组件
    class Leaf extends Component {
        static contextType = BatteryContext;
        render(){
            const battery = this.context;
            return (
                <h1>battery:{battery}</h1>
            )
        }
    }
    
    //子组件
    class Middle extends Component{
        render(){
            return <Leaf/>;
        }
    }
    
    //父组件
    class App extends Component {
        state = {
            battery:50,
            online:false,
        }
        render(){
            const {battery,online} = state;
            //在这里定义 Provider context的提供者
            return (
                <BatteryContext.Provider value = {battery}>
                <OnlineContext.Provider value = {online}>
                    <button type="button" 
                        onclick={()=>{
                            this.setState({
                                battery: battery-1
                            })
                        }}>
                        </button>
                    <button type="button" 
                        onclick={()=>{
                            this.setState({
                                online: !online
                            })
                    }}/>
                    </button>
                    <Middle/>
                </OnlineContext.Provider>
                </BatteryContext.Provider>
    
            )
        }
    }
    export default App;

    2 lazy 加载

    懒加载组件:

    // app.js
    import React,{ Component ,lazy ,Suspense} from 'react';
    //lazy 的返回就是一个react组件
    const About = lazy(()=> import('./About.jsx'));
    
    //父组件 必须用 Suspense 和lazy进行配合,fallback中加载一个实例
    // 或者把 <div>Loading</div> 改为 <Loading/> 组件
    class App extends Component {
        render(){ 
            return (
                <div>
                    <Suspense fallback={<div>Loading</div>}>
                        <About/>
                    </Suspense>    
                </div>
            )
    
        }
    }
    export default App;

    子组件:

    //About.js
    import React,{ Component } from 'react';
    
    //子组件
    export default class About extends Component {
        render(){ 
            return <div>About</div>
        }
    }

    从 开发者工具中的 network 中可以看到:

    如果想改变 trunk 的名字,将app父组件改为下面的方式:

    // app.js
    import React,{ Component ,lazy ,Suspense} from 'react';
    //lazy 的返回就是一个react组件
    const About = lazy(()=> import(/* webpackChunkName:"about"*/'./About.jsx'));

    则:

     如果子组件加载失败,增加容错机制:

    // app.js
    import React,{ Component ,lazy ,Suspense} from 'react';
    //lazy 的返回就是一个react组件
    const About = lazy(()=> import(/* webpackChunkName:"about"*/'./About.jsx'));
    
    // ErrorBoundary 错误边界
    // componentDidCatch 生命周期函数 如果 render() 函数抛出错误,则会触发该函数。
    // 错误在渲染阶段中被捕获,但在事件处理程序中不会被捕获
    class App extends Component {
        state = {
            hasError:false
        };
        componentDidCatch(){
            this.setState({
                hasError:true
            })
        }
        render(){ 
            if(this.state.hasError){
                return <div>error</div>
            }
            return (
                <div>
                    <Suspense fallback={<div>Loading</div>}>
                        <About/>
                    </Suspense>    
                </div>
            )
    
        }
    }
    export default App;

    3。memo

     先看一个示例:父组件每次更新state中的 count,其子组件 Foo 都会执行;

    // App.jsx
    import React,{ Component } from 'react';
    
    //子组件
    class Foo extends Component {
        render(){ 
            console.log('foo render');
            return null;
        }
    }
    
    //父组件
    class App extends Component {
        state={
            count:0,
        }
        render(){ 
            return (
                <div>
                <button onclick={()=>{
                     this.setState({
                         count:this.state.count++
                     })
                }}></button>
                    <Foo name="Milk"/>
                </div>
            )
    
        }
    }
    export default App;

    优化一:使用生命周期函数 shouldComponentUpdate:

    子组件改为:

    //子组件
    class Foo extends Component {
        shouldComponentUpdate(nextProps,nextState){
            if(nextProps.name === this.props.name){
                return false;
            }
            return true;
        }
        render(){ 
            console.log('foo render');
            return null;
        }
    }

    优化二: 使用 PureComponent  ,子组件改为:

    // App.jsx
    import React,{ Component,PureComponent } from 'react';
    
    //子组件
    class Foo extends PureComponent {
        render(){ 
            console.log('foo render');
            return null;
        }
    }

    但是有局限性,只能对传入的属性做简单的对比,如果属性内部发生变化,则不会监听到,导致子组件不会改动:

    // App.jsx
    import React,{ Component,PureComponent } from 'react';
    
    //子组件
    class Foo extends PureComponent {
        render(){ 
            console.log('foo render');
            return <div>{this.props.person.age}</div>;
        }
    }
    
    //父组件
    class App extends Component {
        state={
            count:0,
            person:{
                age:1,
            }
        }
        render(){ 
            const person = this.state.person;
            return (
                <div>
                <button onclick={()=>{
                    person.age++
                     this.setState({
                         person
                     })
                }}></button>
                    <Foo person={person}/>
                </div>
            )
    
        }
    }
    export default App;

    如上所示:改变了父组件中state的 person对象中的age属性,但是子组件是无法监听到的,只能监听到第一级的数据;

    另一个局限:

    父组件给子组件有个内联函数: <Foo person={person} cd={()=>{}}/>  的时候,每次改变父组件的state值,都会创建一个新的函数对象。子组件都会被重新渲染;

    类似的  <Foo person={person} cd={this.callback.bind(this)}/> ,子组件也会被重新渲染。

    改为下面的方法,即可以:

    //父组件
    class App extends Component {
        state={
            count:0,
            person:{
                age:1,
            }
        }
        callback=()=>{
    
        }
        render(){ 
            const person = this.state.person;
            return (
                <div>
                    <Foo person={person} cd={this.callback}/>
                </div>
            )
        }
    }
    export default App;

    将子组件改为无状态函数后,每次改变state 子组件也会改变:

    // App.jsx
    import React,{ Component,PureComponent } from 'react';
    
    //子组件
    function Foo(props) {
        render(){ 
            console.log('foo render');
            return <div>{props.person.age}</div>;
        }
    }
    
    //父组件
    class App extends Component {
        state={
            count:0,
            person:{
                age:1,
            }
        }
        callback=()=>{
    
        }
        render(){ 
            const person = this.state.person;
            return (
                <div>
                    <Foo person={person} cd={this.callback}/>
                </div>
            )
        }
    }
    export default App;

    但是这样的话,每次子组件都会被渲染,这时候 memo 出现了:它相当于 class 中的 PureComponent:

    // App.jsx
    import React,{ Component,PureComponent ,memo } from 'react';
    
    //子组件
    const Foo = memo(
        function Foo(props) {
            render(){ 
                console.log('foo render');
                return <div>{props.person.age}</div>;
            }
        }
    )
    
    
    //父组件
    class App extends Component {
        state={
            count:0,
            person:{
                age:1,
            }
        }
        callback=()=>{
    
        }
        render(){ 
            const person = this.state.person;
            return (
                <div>
                    <Foo person={person} cd={this.callback}/>
                </div>
            )
        }
    }
    export default App;

    这样,拆分的组件传入的属性越简单,越容易写成上述方式;

     6 hooks

    类组件不足:

    1 状态逻辑复用难:缺少复用机制,渲染属性和高阶组件导致层级冗余;

    2 趋向于复杂难以维护: 生命周期函数混杂不相干逻辑,相干逻辑分散在不同生命周期

    3 this 指向困扰:内联函数过度创建新句柄,累成员函数不能保证this;

    (注意:点击组件内的按钮,state中的count发生变化,组件将渲染,但设置默认state只在第一次时渲染)

    // react 分为 聪明组件和傻瓜组件 按我的理解 这个应该用在傻瓜组件中
    // 父组件中 state的变化 导致子组件中 props的变化 导致子组件的重新渲染
    function App(){
        const [count,setCount] = useState(0);
        const [name,setName] = useState('like');
    
        return (
            <button type="button"
            onClick = {()=>{setCount(count++)}}
            >
            {count}
            {name}
            </button>
        )
    }

    问题一:useState设置的默认值,如何知道对应的给state?

    答: useState本身只是单纯的设置一个值,然后是结构赋值功能,赋给了对应的state;

    问题二:每个组件都有useState,它是如何保证只赋值给当前组件中的count,而不是其他组件的count呢?

    答: 利用了js的单线程原理,当前只能赋值给当前作用域下的组件。

    问题三: 如果一个组件有多个useState, 每次渲染该组件的时候,是如何返给定义的state 呢?

    答:useState是根据第一次渲染执行组件的时候,定义的state顺序赋值的,所以不能改变赋值的顺序。例如下面的示例:

    let id = 0;
    function App(){
        let name,setName;
        let count,setCount;
        id++;
        if(id & 1){//如果id是奇数
            [count,setCount] = useState(0);
            [name,setName] = useState('like');
        }else{
            [name,setName] = useState('like');
            [count,setCount] = useState(0);
        }
        return (
            <button type="button"
            onClick = {()=>{setCount(count++)}}
            >
            {count}
            {name}
            </button>
        )
    }

    下面的形式也是不行的:

    let id = 0;
    function App(){
        const [count,setCount] = useState(0);
        const [name,setName] = useState('like');
        id++;
        if(id>1){
            useState('dd'); //从第二次渲染之后 增加的的设置
        }
        return (
            <button type="button"
            onClick = {()=>{setCount(count++)}}
            >
            {count}
            {name}
            </button>
        )
    }

    下面的形式也不行:

    let id = 0;
    function App(){
        const [count,setCount] = useState(0);
        const [name,setName] = useState('like');
        id++;
        if(id===1){
            useState('dd'); //只在第一次渲染时 增加的的设置
        }
        return (
            <button type="button"
            onClick = {()=>{setCount(count++)}}
            >
            {count}
            {name}
            </button>
        )
    }

    比如说 state的默认值是基于 props 的:注意:点击组件内的按钮,state中的count发生变化,组件将渲染,但设置默认state只在第一次时渲染

    function App(){
        const [count,setCount] = useState(()=>{
            return props.defaultCount || 0;   //只会执行一次
        });
        const [name,setName] = useState('like');
    
        return (
            <button type="button"
            onClick = {()=>{setCount(count++)}}
            >
            {count}
            {name}
            </button>
        )
    }

    7 effect hooks 副作用时机

    原来的生命周期:

    Mount之后: componentDidMount

    update 之后 componentDidUpdate

    Unmount 之前 componentWillUnmount

    用 useEffect 函数来替换;

    userEffect函数是在 render之后调用的 ,其功能相当于 componentDidMount/和componentDidUpdate,并且该函数有callback函数,其功能是清除上一次副作用 遗留下来的状态 相当于componentWillUnmount

    示例1: 点击按钮 文本中和页面title均发生变化,使用原来的生命周期开发:

    export default class App extends Component {
        state = {
            count:0
        }
        componentDidMount(){
            doucument.title = this.state.count;
        }
        componentDidUpdate(){
            doucument.title = this.state.count;
        }
        render(){ 
            const { count } = this.state;
            return (
                <button type="button"
                onClick={()=>{
                    this.setState({
                        count:count++
                    })
                }}>
                {count}
                </button>
            )
        }
    }

    可见,为了实现初始化时和数据更新时,title发生变化,同样的交互代码要在两个生命周期中执行两次,类似的 再加上 监听函数,需要在卸载生命周期中 去掉卸载函数:

    export default class App extends Component {
        state = {
            count:0,
            size:{
                with:doucument.doucumentElement.clientWidth,
                height:doucument.doucumentElement.clientHeight
            }
        }
        componentDidMount(){
            doucument.title = this.state.count;
            window.addEvevntListener('resize',this.onResize,false);
        }
        componentwillUnMount(){
            window.removeEventListner('resize',this.onResize,false);
        }
        onResize = ()=>{
            this.setState({
                size:{
                    with:doucument.doucumentElement.clientWidth,
                    height:doucument.doucumentElement.clientHeight
                }
            })
        }
        componentDidUpdate(){
            doucument.title = this.state.count;
        }
        render(){ 
            const { count,size } = this.state;
            return (
                <button type="button"
                onClick={()=>{
                    this.setState({
                        count:count++
                    })
                }}>
                {count}
                size:{size.width}X{size.height}
                </button>
            )
        }
    }

    现在使用 effect hooks:

        //1 提高了代码复用(合并多个生命周期成了一个函数)
        //2 优化了关注点分离,即不同的事件放在了不同的 useEffect 函数中

    function App(){
        //定义初始化数据
        const [count,setCount] = useState(0);
        const [size,setSize] = useState({
            with:doucument.doucumentElement.clientWidth,
            height:doucument.doucumentElement.clientHeight
        });
        //常规函数
        const onResize = ()=>{
            setState({
                with:doucument.doucumentElement.clientWidth,
                height:doucument.doucumentElement.clientHeight
            })
        }
        //1 提高了代码复用(合并多个生命周期成了一个函数)
        //2 优化了关注点分离,即不同的事件放在了不同的 useEffect 函数中
        //使用副作用在某些生命周期中执行数据的操作
        useEffect(()=>{
            doucument.title = count;
        })
        useEffect(()=>{
            window.addEvevntListener('resize',onResize,false);
            return ()=>{ //默认是组件重渲染和组件卸载的时候执行
                window.addEvevntListener('resize',onResize,false);
            }
        },[]);
        //上面useEffect函数的空数组的参数,其作用是用于比对。决定该 useEffect 是否执行
        // 如果第二个参数不写,则每次都会执行这个 useEffect ,如果为空数组,则只执行一次
        // 如果数组中写了数据,则比对每一个数据,只有数组中的每一项都不变的情况下,才会再次执行;
        // 如下面,变化size 不会触发下面useEffect的函数执行
        useEffect(()=>{
            console.log(count);
        },[count])
        
        return (
            <button type="button" onClick = {()=>{setCount(count++)}}>
            {count}
            size:{size.width}X{size:height}
            </button>
        )
    }
    
    export default App;

     8 hooks 环境下的的context

    由前面 context知识可以知道 ContextType 只能存在于 Class中,则hook是的无状态函数咋整?

    下面的示例给出了使用context的三个方法:

    import React,{ Component, useState, createContext, useContext} from 'react';
    
    const CountContext = createContext();//在这理定义context的外层组件
    
    
    //子组件(最基础的写法)
    class Foo extends Component{
        render(){
            return (
                <CountContext.Consumer>
                {
                    count => <h1>{count}</h1>
                }
                </CountContext.Consumer>
            )
        }
    }
    //子组件(优化的写法)适用于类组件
    class Bar extends Component{
        static contextType = CountContext;
        render(){
            const { count} = this.state;
            return (
                <h1>{count}</h1>
            )
        }
    }
    
    //hooks中使用 context 可以获取多个 context
    function Counter(){
        const count = useContext(CountContext);
        return (
            <h1>{count}</h1>
        )
    }
    
    //父组件
    export default class App extends Component {
        const [ count,setCount] = useState(0);
        render(){ 
            return (
                <div>
                    <button type="button"
                    onClick={()=>{setCount(count+1)}}
                    >
                    click({count})</button>
                    <CountContext.Provider value={count}>
                        <Foo/>
                        <Counter/>
                    </CountContext.Provider>
                </div>
            )
        }
    }

     9 hooks中的 useMemo 函数

    不同点:

    • useMemo函数是在渲染过程中执行,同比 useEffect是在渲染后执行;
    • useMemo函数有返回值,同比 useEffect 没有返回值;

    相同点:

      useMemo 函数和 useEffect 函数均有第二个参数,决定是否执行该函数。

    示例:

    import React,{ Component, useState, useMemo} from 'react';
    
    function Counter(props){
        return (
            <h1>{props.count}</h1>
        )
    }
    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3])
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={count}/>
            </div>
        )
    }
    
    export default App;

    如上所示,当 count==3的时候,useMemo中数组的值由 false变为true, double 发生变化

    当 count ==4 的时候, useMemo 中数组的值。由true 变为 false,double 再次发生变化;

    import React,{ Component, useState, useMemo} from 'react';
    
    function Counter(props){
        return (
            <h1>{props.count}</h1>
        )
    }
    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3]);
        // 还可以依赖 memo
        const half = useMemo(()=>{
            return double/4;
        },[double]);
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={count}/>
            </div>
        )
    }
    
    export default App;

     10  hooks中的 callback 函数

    首先看一下memo函数,用memo包裹Counter函数,只有count发生变化的时候,才执行Count函数;

    import React,{ Component, useState, useMemo, memo} from 'react';
    
    const Counter =  memo(function Counter(props){
        cosole.log('count 发生变化的时候才执行');
        return (
            <h1>{props.count}</h1>
        )
    }) 
    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3]);
    
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={double}/>
            </div>
        )
    }

    这时给 子组件 Counte 增加 回调函数 onclick

    import React,{ Component, useState, useMemo, memo} from 'react';
    
    const Counter =  memo(function Counter(props){
        cosole.log('count 发生变化的时候才执行');
        return (
            <h1 onClick={props.onClick}>{props.count}</h1> //这里
        )
    }) 
    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3]);
        const onClick = ()=>{
            console.log('click me');  //父组件中定义回调函数
        }
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={double} onClick={onClick}/> //监听的函数
            </div>
        )
    }
    
    export default App;

    由于回调函数 onclick的存在,每次父组件中app的变化,都 会导致子组件发生渲染;所以可以在父组件中使用 memo

    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3]);
    
        const onClick = useMemo(()=>{
            return ()=>{
              console.log('click me');            
            }
        },[]); //改变了这里
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={double} onClick={onClick}/>
            </div>
        )
    }

    然后使用 useCallback 化简:

    function App(){
        const [count,setCount] = useState(0);
        const double = useMemo(()=>{
            return count*2;
        },[count === 3]);
    
        const onClick = useCallback(()=>{
            console.log('click me');            
        },[]); //改变了这里
        // useMemo(()=>fn);
        // useCallback(fn); useCallback 相当于是简化写法
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),double:({double})
                </button>
                <Counter count={double} onClick={onClick}/>
            </div>
        )
    }

     这样,就不会因为父组件中的 回调函数 onClick的变化导致子组件发生变化:

    1 子组件中使用 memo函数可以避免重复渲染,而是根据传入的props发生变化时才渲染;
    2 父组件中使用 useMemo函数可以避免因为 回调函数的存在,导致子组件的渲染;

    11 hooks中的 useRef

    • 获取子组件或者DOM节点的句柄
    • 渲染周期之间共享数据的存储

    示例1:

    import React,{ Component, PureComponent,useRef} from 'react';//这里引入 useRef组件
    
    class Counter extends PureComponent {
        speak(){
            console.log('speak');
        }
        render(){
            const { props } = this;
            return (
                <h1 onClick={props.onClick}>{props.count}</h1>
         ) } } function App(){ const [count,setCount] = useState(0); const counterRef = useRef();//创建一个ref,在组件中使用该counrerRef const onClick = useCallback(()=>{ counterRef.current.speak();//执行子组件中的speak函数,current属性 获取最终的值 },[counterRef]); return ( <div> <button type="button" onClick={()=>{setCount(count++)}} > click:({count}) </button> <Counter ref={counterRef} count={double} onClick={onClick}/> </div> ) } export default App;

    示例2: 假设组件中定义一个变量,每秒加1,要求大于10之后不再增加;

    function App(){
        const [count,setCount] = useState(0);
        const counterRef = useRef(); 
        let it; //因为更新state,会导致app组件重新渲染,it会重新初始化,而state只在第一次初始化,但是也不便于将it
                //放在state中,毕竟它没有用于渲染组件
        useEffect(()=>{
            it = setInterval(()=>{
                setCount(count=>count+1)
            },1000)
        },[]);
        useEffect(()=>{
            if(count >= 10){
                clearInterval(it); 
            }
        })
    
        const onClick = useCallback(()=>{
            counterRef.current.speak();    
        },[counterRef]); 
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count})
                </button>
                <Counter ref={counterRef} count={double} onClick={onClick}/>
            </div>
        )
    }

    使用ref,将it改为类似于类的结构成员变量:

    import React,{ Component, PureComponent,useEffect,useRef} from 'react';//这里引入 useRef组件
    
    class Counter extends PureComponent {
        speak(){
            console.log('speak');
        }
        render(){
            const { props } = this;
            return (
                <h1 onClick={props.onClick}>{props.count}</h1>
         }
    }
    
    function App(){
        const [count,setCount] = useState(0);
        const counterRef = useRef(); 
        let it = useRef();//改变了这里
        useEffect(()=>{
            it.current = setInterval(()=>{ //改变了这里 it.current
                setCount(count=>count+1)
            },1000)
        },[]);
        useEffect(()=>{
            if(count >= 10){
                clearInterval(it.current);//改变了这里 it.current
            }
        })
    
        const onClick = useCallback(()=>{
            counterRef.current.speak();    
        },[counterRef]); 
    
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count})
                </button>
                <Counter ref={counterRef} count={double} onClick={onClick}/>
            </div>
        )
    }
    
    export default App;

    最后:自定义hooks

    funciton useCount(defaultCount){
        const [count,setCount] = useState(defaultCount);
        const it = useRef();
    
        useEffect(()=>{
            it.current = setInterval(()=>{
                setCount(count=>count +1 );
            },1000)
        },[]);
    
        useEffect(()=>{
            if(count >= 10){
                clearInterval(it.current);
            }
        });
        return [count,setCount]
    }

    示例2 :

    import React,{ Component, PureComponent,useEffect,useRef} from 'react';//这里引入 useRef组件
    
    function useCounter(count) {
        const size = useSize();//共用 useSize函数1
        return (
            <h1>{count},{size.width},{size.height}</h1>
        )
    }
    function useSize(){
        const [size,setSize] = useSize({
             document.documentElement.clientWidth,
            height: document.documentElement.clientHeigth
        })
        const onResize = useCallback(()=>{
            setSize({
                 document.documentElement.clientWidth,
                height: document.documentElement.clientHeigth
            })
        },[]);
        
        useEffect(()=>{
            window.addEventListener('resize',onResize,false);
            return ()=>{
                window.removeEventListener('resize',onResize,false);
            }
        },[])
    }
    funciton useCount(defaultCount){
        const [count,setCount] = useState(defaultCount);
        const it = useRef();
    
        useEffect(()=>{
            it.current = setInterval(()=>{
                setCount(count=>count +1 );
            },1000)
        },[]);
    
        useEffect(()=>{
            if(count >= 10){
                clearInterval(it.current);
            }
        });
        return [count,setCount]
    }
    function App(){
        const [count,setCount] = useCount(0); //这里也是自定义的hooks组件
        const Counter = useCounter(count); // 这里调用的是自定义的hooks函数useCounter
    
        const size = useSize(); //共用 useSize函数2
        return (
            <div>
                <button type="button" onClick={()=>{setCount(count++)}} >
                    click:({count}),<h1>{count},{size.width},{size.height}</h1>
                </button>
                {Counter} //这里调用的 上面 useCounter(count)
            </div>
        )
    }
    
    export default App;

    最后注意:

    一般hooks 都是由 use 为前缀的,一定要遵循:

    1. 把hook 放在最顶层,不要放在条件语句中,因为它依赖顺序;

    2. 仅在组件和自定义hooks组件中调用,不要在其他普通函数中调用,因为普通函数说不清在哪里会被调用,导致hooks的顺序变化,例如

    function useLogin (){ //自定义hooks,在其他地方调用也会是在顶层 有顺序的
        const [login.setLogin] = useState();
        useEffect(()=>{
    
        })
    }
    
    function fetchNews(){//而普通函数,说不清在哪里被调用,有肯能导致顺序不一样
        const [pageNo,setPageNo] = useState();
    }

     ===============分割线

    Hooks 常见问题:

    对传统react 编程的影响

    1 生命周期函数如何映射到hooks

    function App(){
        useEffect(()=>{
            //componentDidMount
            return ()=>{
                //componentWillUnmount
            }
        },[]);//第二个参数为空数组,则只执行一次。挂载时执行一次,卸载时执行一次。
    
        let renderCounter = useRef(0);
        renderCounter.current++;
    
        useEffect(()=>{
            if(renderCounter >1){
                // componentDidUpdate
            }
        }) //没有第二个参数,则每次都执行
    }

    2 类实例成员变量如何映射到hooks?

    答:使用 useRef

    3 Hooks 中如何获取历史props和state

    function Counter(){
        const [count,setCount] = useState(0);
        const preCountRef = useRef();//useRef不会受组件重新渲染的影响,保留上一次的值,所以定义了ref的值 preCountRef
        
        useEffect(()=>{
            preCountRef.current = count; //没有第二个参数,表示每次都执行。则update时将count赋值给ref
        })
        const prevCount = prevCountRef.current;
        return <h1>now:{count},before:{prevCount}</h1>
    }

    4 如何强制更新一个Hooks组件?

    思路:设置一个没有参与渲染的data,然后改变它的值:

    function Counter(){
        const [count,setCount] = useState(0);
        const [updater,setUpdater] = useState(0);
        function forceUpdate(){
            setUpdater(updater => updater+1);//组件没有用到这个data,强制执行该函数,则更新渲染组件
        }
    
        const preCountRef = useRef();
        
        useEffect(()=>{
            preCountRef.current = count; 
        })
        const prevCount = prevCountRef.current;
        return <h1>now:{count},before:{prevCount}</h1>
    }
  • 相关阅读:
    探索Javascript 异步编程
    前端性能调优
    怎样选择前端框架
    前端地图截屏方案
    不一样的山顶角
    前后端分离场景下的另类登录认证方案
    React Diff 算法
    纯css实现Magicline Navigation(下划线动画导航菜单)
    Tinymce group plugin
    自适应process组件
  • 原文地址:https://www.cnblogs.com/xiaozhumaopao/p/10958788.html
Copyright © 2011-2022 走看看