博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 新特性学习
阅读量:7076 次
发布时间:2019-06-28

本文共 24930 字,大约阅读时间需要 83 分钟。

 

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 (            
{ Battery =>

BatteryName:{Battery}

}
) }}//子组件class Middle extends Component{ render(){ return
; }}//父组件class App extends Component { render(){ //在这里定义 Provider context的提供者 return (
) }}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 (            
{ Battery =>

BatteryName:{Battery}

}
) }}//子组件class Middle extends Component{ render(){ return
; }}//父组件class App extends Component { state = { battery:50 } render(){ const {battery} = state; //在这里定义 Provider context的提供者 return (

示例3:多个context嵌套

import React,{ Component,createContext } from 'react';//在这里导出context函数import logo from './logo.svg';import './App.css';const BatteryContext = createContext();//在这里创建contextconst OnlineContext = createContext();//在这里创建context//孙子组件class Leaf extends Component {    render(){        //在这里定义 consumer 消费者        return (            
{ battery => (
{ online =>

battery:{battery},online:{online}

}
) }
) }}//子组件class Middle extends Component{ render(){ return
; }}//父组件class App extends Component { state = { battery:50, online:false, } render(){ const {battery,online} = state; //在这里定义 Provider context的提供者 return (
) }}export default App;

示例4:使用static 化简

import React,{ Component,createContext } from 'react';//在这里导出context函数import logo from './logo.svg';import './App.css';const BatteryContext = createContext();//在这里创建contextconst OnlineContext = createContext();//在这里创建context//孙子组件class Leaf extends Component {    static contextType = BatteryContext;    render(){        const battery = this.context;        return (            

battery:{battery}

) }}//子组件class Middle extends Component{ render(){ return
; }}//父组件class App extends Component { state = { battery:50, online:false, } render(){ const {battery,online} = state; //在这里定义 Provider context的提供者 return (
) }}export default App;

 

2 lazy 加载

 

懒加载组件:

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

子组件:

//About.jsimport React,{ Component } from 'react';//子组件export default class About extends Component {    render(){         return 
About
}}

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

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

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

则:

 

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

// app.jsimport 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 
error
} return (
Loading
}>
) }}export default App;

3。memo

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

// App.jsximport React,{ Component } from 'react';//子组件class Foo extends Component {    render(){         console.log('foo render');        return null;    }}//父组件class App extends Component {    state={        count:0,    }    render(){         return (            
) }}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.jsximport React,{ Component,PureComponent } from 'react';//子组件class Foo extends PureComponent {    render(){         console.log('foo render');        return null;    }}

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

// App.jsximport React,{ Component,PureComponent } from 'react';//子组件class Foo extends PureComponent {    render(){         console.log('foo render');        return 
{
this.props.person.age}
; }}//父组件class App extends Component { state={ count:0, person:{ age:1, } } render(){ const person = this.state.person; return (
) }}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 (            
) }}export default App;

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

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

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

// App.jsximport React,{ Component,PureComponent ,memo } from 'react';//子组件const Foo = memo(    function Foo(props) {        render(){             console.log('foo render');            return 
{props.person.age}
; } })//父组件class App extends Component { state={ count:0, person:{ age:1, } } callback=()=>{ } render(){ const person = this.state.person; return (
) }}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 (            )}

问题一: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 ( )}

 

下面的形式也是不行的:

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

下面的形式也不行:

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

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

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

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 (                    )    }}

可见,为了实现初始化时和数据更新时,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 (                    )    }}

现在使用 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 (            )}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 (            
{ count =>

{count}

}
) }}//子组件(优化的写法)适用于类组件class Bar extends Component{ static contextType = CountContext; render(){ const { count} = this.state; return (

{count}

) }}//hooks中使用 context 可以获取多个 contextfunction Counter(){ const count = useContext(CountContext); return (

{count}

)}//父组件export default class App extends Component { const [ count,setCount] = useState(0); render(){ return (
) }}

 9 hooks中的 useMemo 函数

不同点:

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

相同点:

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

示例:

import React,{ Component, useState, useMemo} from 'react';function Counter(props){    return (        

{props.count}

)}function App(){ const [count,setCount] = useState(0); const double = useMemo(()=>{ return count*2; },[count === 3]) return (
)}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 (        

{props.count}

)}function App(){ const [count,setCount] = useState(0); const double = useMemo(()=>{ return count*2; },[count === 3]); // 还可以依赖 memo const half = useMemo(()=>{ return double/4; },[double]); return (
)}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 (        

{props.count}

)}) function App(){ const [count,setCount] = useState(0); const double = useMemo(()=>{ return count*2; },[count === 3]); return (
)}

 

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

import React,{ Component, useState, useMemo, memo} from 'react';const Counter =  memo(function Counter(props){    cosole.log('count 发生变化的时候才执行');    return (        

{props.count}

//这里 )}) function App(){ const [count,setCount] = useState(0); const double = useMemo(()=>{ return count*2; },[count === 3]); const onClick = ()=>{ console.log('click me'); //父组件中定义回调函数 } return (
//监听的函数
)}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 (        
)}

然后使用 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 (        
)}

 这样,就不会因为父组件中的 回调函数 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 (            

{props.count}

     ) }}function App(){ const [count,setCount] = useState(0); const counterRef = useRef();//创建一个ref,在组件中使用该counrerRef const onClick = useCallback(()=>{ counterRef.current.speak();//执行子组件中的speak函数,current属性 获取最终的值 },[counterRef]); return (
)}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 (        
)}

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

import React,{ Component, PureComponent,useEffect,useRef} from 'react';//这里引入 useRef组件class Counter extends PureComponent {    speak(){        console.log('speak');    }    render(){        const { props } = this;        return (            

{props.count}

}}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 (
)}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 (        

{count},{size.width},{size.height}

)}function useSize(){ const [size,setSize] = useSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeigth }) const onResize = useCallback(()=>{ setSize({ width: 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 (
{Counter} //这里调用的 上面 useCounter(count)
)}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 

now:{count},before:{prevCount}

}

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 

now:{count},before:{prevCount}

}

 

转载于:https://www.cnblogs.com/xiaozhumaopao/p/10958788.html

你可能感兴趣的文章
Hadoop学习笔记一
查看>>
linux压缩和解压缩命令
查看>>
Vue结合webpack实现路由懒加载和分类打包
查看>>
dedecms在后台替换文章标题、内容、摘要、关键字
查看>>
.Net转前端开发-启航篇,如何定制博客园主题
查看>>
DZ论坛如何去掉“今日”“昨日”发帖数显示?
查看>>
xtrabackup 在线主从搭建
查看>>
dm6446开发大全资料号称宇宙最全
查看>>
for循环中变量i值的理解
查看>>
Go之路
查看>>
面向对象(中)之二
查看>>
[C#]获取指定文件夹下的所有文件名(递归)
查看>>
浏览器兼容处理
查看>>
ubuntu下直接可视化访问服务器文件夹方法
查看>>
linux终端下查Dict.cn/WebsterOnline/Etymonline.com
查看>>
如何使用ASP.NET开发基于推技术的聊天室?
查看>>
redis-主从复制
查看>>
《小账本》开发日志 第六天
查看>>
第九周总结
查看>>
173. Binary Search Tree Iterator
查看>>