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

 

posted @ 2019-06-01 10:43  小猪冒泡  阅读(701)  评论(0编辑  收藏  举报