美文网首页
react-03-生命周期和组件通信

react-03-生命周期和组件通信

作者: 未来在奋斗 | 来源:发表于2019-12-16 17:01 被阅读0次

    生命周期与钩子函数(重点)

    生命周期指的react实例及组件从创建到运行到销毁的完整的过程。

    组件的生命周期可分成三个阶段(状态):

    • Mounting:创建阶段:已插入真实 DOM
    • Updating:运行阶段:正在被重新渲染
    • Unmounting:销毁阶段:已移出真实 DOM

    钩子函数指提前埋在生命周期中的函数,等到程序运行到这一刻时,它会自动执行。

    常用的钩子函数

    componentWillMount、componentWillReceiveProps、shouldComponentUpdate、ComponentDidMount

    代码示例:

    父组件代码

    class Comp1 extends React.Component{
        constructor(){
            super();
            this.state = {
                a:1,
                isShow:true
            }
        }
        up_click(){
            this.setState(state=>({
                a:state.a+1
            }));
        }
        un_click(){
            this.setState(state=>({
                isShow:!state.isShow
            }));
        }   
        render(){
            return (<div>
                <input type="button" onClick={()=>{this.up_click()}} value="更改组件2的属性" />
                <input type="button" onClick={()=>{this.un_click()}} value="卸载组件2" />
                {this.state.isShow ? <Comp2 b={this.state.a} /> : null}
            </div>);
        }
    }
    ReactDOM.render( <Comp1 />, document.getElementById('root'));
    

    子组件代码

    class Comp2 extends React.Component{
        // 创建阶段
        constructor(){
            super();
            console.log("constructor");
        }
        componentWillMount(){
            console.log("componentWillMount在渲染前调用");
        }
        render(){
            console.log("render");
            return <div id='div2'>comp2-{this.props.b}</div>
        }
        componentDidMount(){
            console.log("componentDidMount在第一次渲染后调用");
        }
    
        // 运行中阶段
        componentWillReceiveProps(newProps) {
            console.log(`newProps: ${newProps}
            在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。`)
        }
        shouldComponentUpdate(newProps, newState) {
            console.log(`newProps: ${newProps} newState: ${newState} 
            返回一个布尔值。在组件接收到新的props或者state时被调用。
            在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。`)
            return true;    // true表示更新组件;false表示不更新组件
        }
        componentWillUpdate(nextProps, nextState) {
            console.log(`nextProps: ${nextProps} nextState:${nextState} 
            在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。`);
        }
        componentDidUpdate(prevProps, prevState) {
            console.log(`prevProps:${prevProps} prevState:${prevState} 
            在组件完成更新后立即调用。在初始化时不会被调用。`)
        }
    
        // 销毁阶段
        componentWillUnmount() {
            console.log('在组件从 DOM 中移除的时候立刻被调用')
        }
    }
    

    新增的生命周期钩子函数

    在 react v16.3 时,新引入了新的生命周期函数:getDerivedStateFromProps,getSnapshotBeforeUpdate。

    在未来的 react v17 时,componentWillMount、componentWillReceiveProps、componentWillUpdate 要被废弃。

    getDerivedStateFromProps

    // 父组件的state发生变化,导致父组件中的当前组件被重新渲染,当前组件的props被修改时,该钩子函数会被触发。
    /*componentWillReceiveProps(nextProps){
        console.log('nextProps: ', nextProps);
    }*/
    
    
    // 不仅仅具有componentWillReceiveProps的能力,自身组件state变化时,该钩子函数也会被触发。
    // 该函数在shouldComponentUpdate之前执行。
    // static描述是静态函数,其没有this指向,所以无权操作实例,所以更安全,而且消耗性能低。
    // nextProps 传入后的prop数据,即最新的props
    // prevState 相对于合并的state来说的前一个状态
    static getDerivedStateFromProps(nextProps, prevState) {
        console.log('nextProps: ', nextProps);
        console.log('prevState: ', prevState);
        return {x:nextProps.a} // 合并到当前组件的state
        //return null
    }
    

    配合 componentDidUpdate 周期函数,getDerivedStateFromProps 是为了替代 componentWillReceiveProps 而出现的。它将原本 componentWillReceiveProps 功能进行划分 —— 更新 state 和 操作/调用 props,很大程度避免了职责不清而导致过多的渲染, 从而影响应该性能。

    getSnapshotBeforeUpdate

    在 render 之后执行的钩子

    // 更新之前
    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log('getSnapshotBeforeUpdate')
        console.log('prevProps:', prevProps)
        console.log('prevState:', prevState)
        return {x:1}
    }
    
    // 更新之后 snapshot能够得到 getSnapshotBeforeUpdate 的返回值
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('componentDidUpdate')
        console.log('prevProps:', prevProps)
        console.log('prevState:', prevState)
        console.log('snapshot:', snapshot)
    }
    

    性能优化 shouldComponentUpdate()

    决定视图是否需要重新渲染

    改变a时,render重新执行;改变b时,render不会重新执行

    class App extends Component {  
        constructor(){
            super();
            this.state = {
                a : 1,
                b : 1
            }
            this.fna = ()=>{
                this.setState({ a: new Date().getTime() })
            }
            this.fnb = function(){
                this.setState({ b: new Date().getTime() })
            }
        }
        shouldComponentUpdate(nextProps, nextState){
            if( this.state.a !== nextState.a ){
                return true;
            }else{
                return false;
            }
        }
        render(){
            return <div>
                a: {this.state.a}<br />
                b: {this.state.b}<br />
                <input type="button" value="改变a" onClick={this.fna} />
                <input type="button" value="改变b" onClick={()=>this.fnb()} />
            </div>;
        }
    }
    

    纯组件 PureComponent(浅比较)

    pure 是纯的意思,PureComponent 也就是纯组件

    浅比较,如果是PureComponent,那么执行add时,视图不会更新;

    在修改纯组件中的状态时,检查更新前后的状态是否一致(栈中比较),如果一致,则不更新视图,如果不一致,才更新视图。

    而如果是React.Component,那么当执行add时,视图会自动更新。

    比较修改状态前后是否一致时,在堆中比较。

    class App extends React.PureComponent {
        constructor(props) {
            super(props);
            this.state = { 
                a : 1,
                arr : ['a','b','c']    
            }
        }
        updata(){
            this.setState({
                a: this.state.a+1
            })
        }
        add(){
            this.setState(state=>{
                state.arr.push( new Date().toLocaleString() );
                return state;
            })
        }
        render() {
            return (
                <ul>
                    <li>
                        <input
                            type="button" 
                            value={'修改:'+this.state.a}
                            onClick={this.updata.bind(this)} />
                        <input
                            type="button"
                            value="添加"
                            onClick={this.add.bind(this)} />
                    </li>
                    { this.state.arr.map((item, ind)=><li key={ind}>{item}</li>)  }
                </ul>
            );
        }
    }
    

    使用 PureComponent 可能导致不自动更新页面

    因为PureComponent是浅比较,所以对数组和对象的更新,如果只是改变了堆中数据,那么系统是不会自动触发render函数的,就不会自动更新了,这个过程在react中被称为数据的突变。

    把PureComponent换成Component就不会出现这个问题了,但Component属于深比较,性能消耗的多一些。

    不会突变的数据力量

    PureComponent浅比较中如何触发render?

    只要改变了栈中的数据,视图层就会自动更新。

    this.setState(prevState => ({
        words: prevState.words.concat(['marklar'])
    }));
    
    this.setState(prevState => ({
        words: [...prevState.words, 'marklar'],
    }));
    
    Object.assign({}, colormap, {right: 'blue'});
    

    以上代码都可以在PureComponent组件中,触发render。

    父组件向子组件传递数据(重点)

    react 和 vue 和 angular 一样,都是单项数据流,所以项目中推荐的是父向子传递数据。

    状态提升

    非父子组件数据通信时,把一些共有的数据放到共有的节点中。

    爷爷、大伯、父亲、孩子

    孩子:修改大伯的状态时,应该把大伯的状态提升到爷爷上,然后由爷爷以属性的形式,把方法先传给父亲,然后父亲以属性的形式把方法传给孩子,孩子触发该方法,就能触发爷爷上的方法,爷爷修改了状态,重新传给大伯,大伯重新渲染页面。

    父组件在属性上描述想要传入的数据即可

    <abc xyz="123"></abc>
    

    子组件使用 props 接收传入的数据即可

    this.props.xyz
    

    子组件向父组件传递数据

    父组件:

    fn(a, b){
        alert(a+b)
    }
    render(){
        return ( <div>
            父组件 <br/>
            <abc fn={this.fn.bind(this)}></abc>
        </div> )
    }
    

    子组件:

    <button onClick={()=>{ this.props.fn(1,2) }} >按钮</button>
    

    EventBus 中央事件总线

    非父子组件,数据通信,eventbus。

    bus.js

    import { Component } from 'react'
    import { EventEmitter } from 'events'
    const bus = new EventEmitter();
    Component.prototype.$bus = bus;
    

    index.js

    import './modules/bus.js'
    

    创建自定义事件

    this.$bus.on('abc', function(){})
    

    触发自定义事件

    this.$bus.emit('abc')
    

    emitter对象下还有once、off等方法

    Context 状态树

    context 状态树虽然没有被废除,但官方是不建议使用的。

    解决的是复杂组件关系时,数据共享的问题,官方建议用eventbus或redux来解决。

    Provider 提供者;Consumer 消费者

    // 创建名字叫做colorContext的状态树上下文对象,默认值为red。
    const colorContext = React.createContext('red');
    
    // 创建外层组件(Provider提供了一些数据共享的能力,表示colorContext这颗状态树对象的值设置为yellow)
    // 即,当前组件的后代组件,都可以通过Consumer来使用共享中的数据,即yellow这个数据。
    class Container extends Component {
        render(){
            return <colorContext.Provider value='yellow'>
                <div> Container
                    <Temp2></Temp2>
                </div>
            </colorContext.Provider>
        }
    }
    
    // 创建中间层组件(复杂的组件关系时,这可能是很多层,如果使用context,就不需要一层一层的传递props了)
    class Temp2 extends Component {
        render(){
            return <div> temp
                <Box></Box>
            </div>
        }
    }
    
    // 创建内层组件(在这层组件中,使用前面提供的数据)
    class Box extends Component {
        render(){
            return <colorContext.Consumer>
                {c=><div> box
                    <div style={{background:c}}>{c}</div>
                </div>}
            </colorContext.Consumer>
        }
    }
    

    把 Container 类中的 colorContext 这个标签去掉,直接在 Box 类中用 colorContext 就能够看到默认值了,注意return 后面不能有换行。

    HOC 高阶组件

    高阶组件(HOC)是react中的高级技术,用来重用组件逻辑。

    高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。

    // 原始组件
    class OldComp extends Component {   
        render(){
            return <div>old</div>
        }
    }
    
    // 高阶组件
    function higherOrderComponent(Comp){
        return class extends React.Component {
            render(){
                return <Comp />;
            }
        }
    }
    
    // 新组件
    const NewComp = higherOrderComponent(OldComp);
    
    // App组件的渲染
    class App extends Component {  
        render(){
            return <NewComp />;
        }
    }
    

    Slot 插槽

    基本

    组合---包含关系---slot

    class Component1 extends Component {   
        render(){
            return <div>
                { this.props.children }
            </div>;
        }
    }
    
    class App extends Component {
        constructor(){
            super();
            this.state = {}
        }
        render(){
            return (            
                <Component1>
                    <h1>标题</h1>
                    <p>内容</p>
                </Component1>
            );
        }
    }
    

    多个

    class Component1 extends Component {   
        render(){
            return <div>
                { this.props.left }
                { this.props.right }
                { this.props.children }
            </div>;
        }
    }
    
    class App extends Component {
        constructor(){
            super();
            this.state = {}
        }   
        render(){
            return (            
                <Component1
                    left={<div>你好</div>}
                    right={<div>hello</div>}
                >
                    children仅解析这内容,不会取left和right
                </Component1>
            );
        }
    }
    

    Flux 状态管理

    Flux 是比较旧的一种状态管理技术,现在 react 官方已经不推荐使用了。

    网上看到的 flux 教程几乎都是2015-2017年的,而当时的 react 和现在的 react 的代码写法上有很大区别。

    下面是新代码的写法。

    App.js 组件视图层入口

    用户打开浏览器后看到的页面,这个页面有2部分功能。

    • 从 store 中获取数据,渲染到当前组件的视图层上。
    • 点击按钮,触发 action 中的方法,这个方法改变 store 中的数据。(store数据改变后,当前 App 组件重新渲染)
    import React, { Component } from 'react';
    
    // 所有的动作
    import Actions from './store/actions';
    
    // 仓库,这里保存的是数据和操作数据的方法
    import store from './store/store';
    
    // 组件
    class App extends Component {
        constructor(){
            super();
            this.state = {
                todos : store.todos
            }
        }
        
        // 执行 action 中的方法
        add(){
            Actions.add('你好');
        }
        
        // 注册一个回调函数,因为 flux 中的数据修改后,不会自动更新视图,
        // 所以向 store 中注册一个函数,
        // 等 store 中数据发生变化后,要调用这个回调函数,进而修改当前视图。
        componentDidMount(){
            store.change(()=>{
                this.setState({
                    todos : store.todos    
                })
            })
        }
        render() {
            return (
                <div>
                    <button onClick={()=>{this.add()}}>添加</button>
                    { this.state.todos.map(item=><li key={item.id}>
                        {item.text}
                    </li>) }
                </div>
            );
        }
    }
    
    export default App;
    

    action.js 动作

    动作页面,所有操作 flux 的动作都写在此处,比如对 store 中某数据的增删改查操作的动作。

    实际上是调用 dispatcher 的 dispatch 方法。

    import appDispatcher from './dispatcher';
    
    export default {
        add( val ){
            appDispatcher.dispatch({type:'ADD', val});
        }
    }
    

    dispatcher.js 派发

    只做一些派发,业务逻辑都写在 store 中

    // npm i flux  or  yarn add flux
    import { Dispatcher } from 'flux';
    import store from './store';
    
    // 创建 dispatcher
    const appDispatcher = new Dispatcher();
    
    // 当用户执行appDispatcher.dispatch时,实际上执行的就是下面注册进来的函数
    appDispatcher.register(action => {
        // console.log('dispatcher -> action:', action);
        switch( action.type ){
            case 'ADD':
                store.addTodo( action.val );
                store.emit('change'); // 触发用户提交过来的回调函数
                break;
        }
    })
    
    export default appDispatcher;
    

    store.js 仓库

    仓库,存储数据的容器。

    import { EventEmitter } from 'events';
    
    // 创建store对象,让store对象具有on和emit方法(Object.assign是将对象进行合并)
    const store = Object.assign({}, EventEmitter.prototype, {
        todos : [],
        addTodo( val ){
            this.todos.push({text:val, id:new Date().getTime()})
        },
        change( callback ){
            this.on('change', callback);
        }
    });
    
    export default store;
    

    Yarn

    yarn 和 npm 一样,都是包管理工具,解决的都是在项目中,对文件的上传、下载、依赖描述等等相关问题。

    作用 npm Yarn
    安装 npm install(i) yarn
    卸载 npm uninstall(un) yarn remove
    全局安装 npm install xxx –-global(-g) yarn global add xxx
    安装包 npm install xxx –save(-S) yarn add xxx
    开发模式安装包 npm install xxx –save-dev(-D) yarn add xxx –dev(-D)
    更新 npm update –save yarn upgrade
    全局更新 npm update –global yarn global upgrade
    卸载 npm uninstall [–save/–save-dev] yarn remove xx
    清除缓存 npm cache clean yarn cache clean
    重装 rm -rf node_modules && npm install yarn upgrade
    react生命周期.png

    相关文章

      网友评论

          本文标题:react-03-生命周期和组件通信

          本文链接:https://www.haomeiwen.com/subject/lpfenctx.html