React 新特性学习

 

1 context

2 contextType

3 lazy

4 suspense

5 memo

6 hooks

7 effect hooks

===========

1 Context

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

React 新特性学习_第1张图片

 

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 => 

BatteryName:{Battery}

} BatteryContext.Consumer> ) } } //子组件 class Middle extends Component{ render(){ return ; } } //父组件 class App extends Component { render(){ //在这里定义 Provider context的提供者 return ( <BatteryContext.Provider value = {60}> 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 (
            
            {
                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();//在这里创建context
const 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();//在这里创建context
const 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.js
import 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.js
import React,{ Component } from 'react';

//子组件
export default class About extends Component {
    render(){ 
        return 
About
} }

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

React 新特性学习_第2张图片

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

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

则:

React 新特性学习_第3张图片

 

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

// 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 
error
} return (
Loading
}>
) } } 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 (
            
) } } 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 
{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属性,但是子组件是无法监听到的,只能监听到第一级的数据;

另一个局限:

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

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

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

//父组件
class App extends Component {
    state={
        count:0,
        person:{
            age:1,
        }
    }
    callback=()=>{

    }
    render(){ 
        const person = this.state.person;
        return (
            
this.callback}/>
) } } export default App;

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

// App.jsx
import 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 (
this.callback}/>
) } } 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 
{props.person.age}
; } } ) //父组件 class App extends Component { state={ count:0, person:{ age:1, } } callback=()=>{ } render(){ const person = this.state.person; return (
this.callback}/>
) } } 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 函数来替换;

React 新特性学习_第4张图片

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是的无状态函数咋整?

React 新特性学习_第5张图片

下面的示例给出了使用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 可以获取多个 context function 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 (
double}/>
) }

 

这时给 子组件 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 (
double} onClick={onClick}/> //监听的函数
) } 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 (
        
double} onClick={onClick}/>
) }

然后使用 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 (
        
double} onClick={onClick}/>
) }

 这样,就不会因为父组件中的 回调函数 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 (
ref={counterRef} count={double} onClick={onClick}/>
) } 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 (
        
double} onClick={onClick}/>
) }

使用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 (
double} onClick={onClick}/>
) } 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

你可能感兴趣的:(React 新特性学习)