Skip to content

Latest commit

 

History

History
261 lines (224 loc) · 9.04 KB

十分钟,带你了解React 16.8的useState和useEffect.md

File metadata and controls

261 lines (224 loc) · 9.04 KB
title date tags summary
十分钟,带你了解React 16.8的useState和useEffect
2019-08-27
React
Javascript
TenMinutes
Hooks是React 16.8新增的特性,并且他是向后兼容的,facebook也坦言没有计划会在React中移除class组件。你完全可以不使用hooks,一直使用class组件。

HooksReact 16.8新增的特性,并且他是向后兼容的,facebook也坦言没有计划会在React中移除class组件。你完全可以不使用hooks,一直使用class组件。

概念

React hooks允许你在非class组件中使用stateReact其他的特性:propscontextrefs 以及生命周期Hook本质上是一些函数,可以在函数组件中“钩入”其他React的特性,使得你不用使用class也可以使用React。值得注意的是,hooks是不能在class组件中使用的。

State Hook

这里贴一下官网文档中用hooks实现一个计数器的代码

// Hooks
import React, {useState} from 'react'

function Test(){
    // 这里生命了一个“count”的state,和一个可以修改count的方法
    const [count, setCount] = useState(0); // 这里传入count这个state的默认值

    return (
        <div>
            <p>You clicked {count} times.</p>
            <button onClick={setCount(count + 1)}>Add Count +1</button>
        </div>
    )
}

useState就是一个hooks,他接收一个初始化state的参数,并返回一个当前state的值和一个可以设置state的方法。借用es6的数组解构语法,我们可以很优雅的用变量名来接收他们。就像上面的countsetCount。在此期间,我们没有声明任何class组件 ,也不必去写constructor这些方法和声明其他的state

当然,在这里你也可以一直使用class,而不去拥抱hooks,所以可能写出来的代码,看起来会像这样

// class
import React, {Component} from 'react'

class Test extends Component{
    // 这里要初始化state
    constructor(){
        super();
        this.state = {
            count: 0
        }
        // 也别忘记了要把方法绑定到实例里。
        this.handleClickAddCount = this.handleClickAddCount.bind(this);
    }
    handleClickAddCount(){
        // 这里要用setState返回state的更改
        this.setState(({count}) => ({
            count: count + 1
        }))
    }
    render(){
        return (
            <div>
                <p>You clicked {this.state.count} times.</p>
                <button onClick={this.handleClickAddCount}>Add Count +1</button>
            </div>
        )
    }
}

上面的class组件,只不过是因为需要有一个countstate数据,就大费周章地写下了这么多代码,想到这里,你是不是对hooks有一点好感了?

Effect Hooks

在React中,仅仅通过state状态的更改来操作DOM,可能还不是我们想要的。我们可能需要做更多“副作用”的东西,譬如在某个state变更的时候,同步更改页面的title

贴下useEffect这个hooks的代码

function Test(){
	const [count, setCount] = useState(0);
	// 相当于 componentDidMount 和 componentDidUpdate:
	useEffect(() => {
		// 使用浏览器的 API 更新页面标题
		document.title = `You clicked ${count} times`;
		console.log('mkLog: count', count);
	});

	return (
		<div>
			<p>You clicked {count} times</p>
			<button onClick={() => setCount(count + 1)}>Click me</button>
		</div>
	)
}

这样组件在挂载完毕和count改变的时候都会对网站的标题进行设置了,如果用以前的class方式声明组件的话,就发现一些冗余

class Test(){
    constructor(){
        super();
        this.state = {
            count: 0,
        }
        this.handleClickAddCount = this.handleClickAddCount.bind(this);
    }
    componentDidMount(){
        const {count} = this.state;
		document.title = `You clicked ${count} times`;
    }
    componentDidUpdate(){
        const {count} = this.state;
		document.title = `You clicked ${count} times`;
    }
    // 两个生命周期中执行了同样的代码
    handleClickAddCount(){
        this.setState(({count}) => ({
            count: count + 1
        }))
    }
    render(){
        return (
            <div>
                <p>You clicked {this.state.count} times.</p>
                <button onClick={this.handleClickAddCount}>Add Count +1</button>
            </div>
        )
    }
}

稍有react开发经验的同学应该对这个场景很熟悉了,componentDidMountcomponentDidUpdate里面运行的是同样的代码,使用这个hooks可以很明显的减少代码量和复杂度。

除此之外,useEffect还能实现componentWillUnmount这个生命周期,只是这里有一点区别,放一个官网文档的例子来帮助理解一下: 用class实现一个显示好友是否在线的组件。

// ChatAPI 假设是一个(取消)订阅好友的api方法
// 组件挂在的时候,订阅好友的状态
componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}
// 当组件卸载的时候,取消订阅组件的订阅
componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}

但是这样就会有一个问题,就是当组件挂载之后,已经显示在屏幕上了,props中的friend已经发生改变了,这时组件展示的还是原来的好友的状态,而且还会因为卸载时取消了错误的好友ID导致内存泄露或崩溃的问题。 这就使得我们不得不在这个class组件中添加componentDidUpdate来解决问题

// 新加一个props的friend更新时的生命周期钩子
componentDidUpdate(prevProps) {
    if(prevProps.friend.id === this.props.friend.id) return;
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
        prevProps.friend.id,
        this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}

// 当组件卸载的时候,取消订阅组件的订阅
componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
        this.props.friend.id,
        this.handleStatusChange
    );
}

这个场景相信各位用React开发的同学也都会遇到,无形之中多了很多重复的冗余代码,下面展示下useEffect的下个特性,可以完美地解决上述的问题

function FriendStatus(props) {
    // 使用
    useEffect(() => {
        // 这里会在原来componentDidMount和ComponentDidupdate时调用
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // ⑴
        // 这里会在componentDidUpdate和componentWillUnmount时调用
        return () => { // ⑵
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });
}

这里有三个需要注意的地方,第一点在组件挂载的时候,仅会执行⑴处代码,而不会执行⑵处的回调函数;第二点,在组件props改变的时候,也就是发生类似class组件中的componentDidUpdate的时候,⑵的回调函数会比⑴早一步执行;第三点,组件卸载的时候,也就是等同于class中的componentWillUnmound的时候,useEffect只会调用⑵的回调函数而不会调用⑴处的代码。

另外useEffect的第二个可以传递一个数组进去,表示数组中的state变量发生变化时才调用⑵的函数.

function Test(){
	const [count, setCount] = useState(0);

	useEffect(() => {
		document.title = `You clicked ${count} times`;
	}, [count]); // Mark: 传递count解绑

    // rest code...
}

留点家庭作业,尝试着自己设计一个例子来验证上述的结论

最好自己思考过来再来参考下面的代码:

import React, {useState, useEffect} from 'react'
import ReactDOM from 'react-dom'

function Test(){
	const [count, setCount] = useState(0);
    useEffect(() => {
        console.log('mount/change');
        
        return () => {
            console.log('done');
        }
	})
	return (
		<div>
			<button onClick={() => {setCount(count + 1)}}>{count}</button>
		</div>
	)
}

function Wrap(){
    // 这里复习下useState
    const [isShow, changeShow] = useState(true);

    return (
        <div>
            {isShow && <Test/>}
            <button onClick={() => changeShow(!isShow)}>{isShow ? 'hide' : 'show'}</button>
        </div>
    )
}

ReactDom.render(
    <Wrap/>,
    document.querySelectot('#root')
)

每次花个十分钟,懂一个React知识点,走得虽慢,只要坚持走下去,足以致千里。

关注本人公众号