2018年2月3日星期六

React 导读(二)

前言

在上篇文章React 导读(一)中学习到了写第一个 Web 组件,这篇文章将继续之前的目录,开始新的知识点补充:

  1. React 如何编写 Hello World!
  2. React 中三个最基础、最重要的东西
  3. React 中的 JSX
  4. 你的第一个 Web 组件
  5. React 中最开始需要关注的生命周期
  6. React 一个组件集合的简单交互
  7. React 开始一个项目的一点建议
  8. React 简单的项目结构组织

五、React 中最开始需要关注的生命周期

其实在学习 React 之前,就应该了解目前前端推荐的是组件化开发的方式,React 是让组件化更加简单的库。那么组件开发必不可少的就是生命周期,说直白一点就是运行组件的过程是 React 来做,运行过程中需要有一些代码钩子来让我们去调用,在组件执行的某一个地方去执行我们自己写的代码。这里先介绍拥有的生命周期钩子,下面的方法 constructorrender 不属于生命周期,我按功能分类了一下,也就是学习的时候不一定要按部就班,应该以学习之后能真正写一些东西为目标:

(1) 与组件挂载相关的方法,包括构造函数

  • constructor
  • componentWillMount
  • componentDidMount
  • componentWillUnmount

最常用的生命周期应该是最后 2 个,constructorcomponentWillMount 目前先理解成能满足的功能大体相同,如果这里解释太复杂不太好。

对于最开始关注的是:this.state 的初始化以及 ajax 在哪里请求。

this.state 在 constructor 进行初始化,ajax 推荐在 componentDidMount 中进行请求。

  • componentDidMount 就是在组件已经挂载到 DOM 中后的钩子,可以理解为 jQuery 中提供的 ready 方法。
  • componentWillUnmount 是在组件即将被卸载前一刻的钩子,一般用于取消 componentDidMount 中订阅的事件等作用,清理一些不要的变量等,避免内存泄漏。

下面通过一个简单的例子说明一下:

先有一个 foods.json 文件来模拟请求的后台数据。

[
    {
        "id": 1,
        "name": "香蕉"
    },
    {
        "id": 2,
        "name": "苹果"
    },
    {
        "id": 3,
        "name": "猕猴桃"
    }
]
// 1. 挂载相关的生命周期
class CycleMount extends React.Component {
    constructor() {
        super();
        this.state = {
            foods: []
        };

        console.log('1. constructor 执行了...');
    }
    componentDidMount() {
        // 这里使用原生的 fetch API 进行 ajax 请求,你也可以使用 $.ajax 进行请求,原理是一样的,重点是关注 setState 的地方
        fetch('/mock/foods.json',
            {
                method: 'GET',
                headers: new Headers({
                    'Accept': 'application/json'
                })
            }
        ).then(dataResult => {
            if(dataResult.status === 200) {
                return dataResult.json();
            } else {
                return [];
            }
        }).then(data => {
            // 这里的 data 就是 foods.json 里面的数据
            // 调用 setState 来更新 render 里面调用的 this.state 的值
            this.setState({
                foods: data
            });
        });

        console.log('2. componentDidMount 执行了...');
    }
    render() {
        // foods 是一个数组,map 方法是数组自带的方法,可以查询相关 api
        const foodItems = 
            this.state.foods.map(food => {
                return (<li key={food.id}>{food.name}</li>);
            });

        // 这里是返回的最终组件结构
        return (
            <ul>
                {foodItems}
            </ul>
        );
    }
}

上面有了完整的注释,也能看到基本上项目中可能会将代码写到何处,我也打了两个日志,来识别到底是谁先执行,结果可以自己运行一下,执行顺序就是我标记的1,2。

好了,基本的学习了,可以自己动手试试订阅一个事件,然后在卸载的时候取消这个事件。

(2) 优化相关

  • shouldComponentUpdate

这个方法比较重要,但是我这里不会介绍得太过于复杂,太复杂只会让重要的部分不那么突出。这个方法的返回值是一个 boolean 类型,分别代码的意义:

  • true 组件应该更新,执行 render 方法以及相关生命周期;
  • false 组件状态没有更新,不执行 render 等方法,意味着网页界面不会改变。

那么它直观上的作用是能够通过返回值来决定界面是否改变,实际的意义就是当我们知道当前 oldState = this.state 的值和新的 newState = this.state 值完全相等的时候(或者是新传入的 props)就不用再浪费性能去重新渲染组件了。

API 上的定义是这样的:

/* nextProps: 新的 props, nextState: 新的 state */
shouldComponentUpdate(nextProps, nextState): boolean

举个例子来直观说明一下:


class ComponentOptimize extends React.Component {
    state = {
        count: 0
    };
    shouldComponentUpdate(nextProps, nextState) {
        // 当 count > 10 的时候就不能再重新渲染组件了
        if(nextState.count > 10) {
            return false;
        }

        return true;
    }
    handleUpdateCount() {
        console.log('我点了一下哦!');
        this.setState(prevState => {
            return {
                count: prevState.count + 1
            };
        });
    }
    render() {
        return (
            <div onClick={this.handleUpdateCount.bind(this)} style={{cursor: 'pointer'}}>
                <h1>{this.state.count}</h1>
            </div>
        );
    }
}

你会发现 10 过后界面就没有再更新过了,这样应该很直观了。

(3) Props 相关的生命周期

  • componentWillReceiveProps

这个主要是在组件的 props 传入新的值后被调用,不管是不是传的一样的值或者 shouldComponentUpdate 返回了 false,看下例子吧:

class Cat extends React.Component {
    componentWillReceiveProps(nextProps) {
        console.log('改一次我执行一次!');
    }
    shouldComponentUpdate(nextProps, nextState) {
        // 改的名字一样的时候
        return this.props.name !== nextProps.name;
    }
    render() {
        console.log('猫改了一次名字!');
        return (
            <h1>我有新名字了!{this.props.name}</h1>
        );
    }
}

class App extends React.Component {
    state = {
        catName: '噜噜'
    };
    handleChangeCatName() {
        const catNames = ['噜噜', '小白', '小黄', '小黑', '皮卡丘'];
        const catIndex = this.getSomeOneIndex();

        this.setState({
            catName: catNames[catIndex]
        });
    }
    getSomeOneIndex() {
        return Math.floor(Math.random() * 5 + 0);
    }
    render() {
        return (
            <div>
                {/* 给 Cat 传新的名字 */}
                <Cat name={this.state.catName} />
                <button onClick={this.handleChangeCatName.bind(this)}>点我给猫咪取新名字!</button>
            </div>
        );
    }
}

最后肯定每次点击按钮都会输出这句的结果 console.log('改一次我执行一次!');

(4) 更新组件相关

  • componentWillUpdate
  • componentDidUpdate

因为都是讲解 API,所以国际惯例的先看下 API 的定义吧:

// 组件更新前执行
componentWillUpdate(nextProps, nextState)
// 组件更新后执行
componentDidUpdate(prevProps, prevState)

可以从定义中看出,它们都接受了两个参数:props && state,不过看变量前缀能够联想点什么。

暂时想不到什么实际项目的例子,随便假设点内容吧。不过这里需要注意的地方是:

  • 1. 这两个方法里面都不要调用 setState!
  • 2. 第一次初始化组件 render 的时候不会执行
  • 3. shouldComponentUpdate 返回 false 不会执行

第一条的原因:容易形成一个递归的调用,不作就不会死...所以尽量不要在这里调~目前还没有碰到需要在这里调的需求。
第二条的原因:额,说好的更新才调,初始化不调用是符合逻辑的。
第三条的原因:额,这 2 个钩子是与组件更新相关的,所以也符合逻辑的,组件是否更新就是靠 shouldComponentUpdate 返回值。

在上面 Cat 的例子中加入下面的代码可以看下结果:

componentWillUpdate() {
    console.log('componentWillUpdate 执行了!')
}
componentDidUpdate() {
    console.log('componentDidUpdate 执行了!')
}

(5)组件错误

  • componentDidCatch

就是在组件发生异常的时候可能会被调用的钩子,需要注意的有下面的地方:

  • 只能在父级组件捕获子组件的异常;
  • 如果异常被 try...catch 包裹父级组件的钩子就不会执行了。

看个例子吧:

class Cat extends React.Component {
    componentWillReceiveProps(nextProps) {
        // 这里手动抛一个异常,触发我们的钩子 componentDidCatch
        throw new Error('miao miao~');
    }
    render() {
        let miao = this.props.name;

        return (
            <div>
                {miao}
            </div>
        );
    }
}

class App extends React.Component {
    state = {
        catName: '噜噜',
        isError: false,
    };
    handleChangeCatName() {
        const catNames = ['噜噜', '小白', '小黄', '小黑', '皮卡丘'];
        const catIndex = this.getSomeOneIndex();

        this.setState({
            catName: catNames[catIndex]
        });
    }
    getSomeOneIndex() {
        return Math.floor(Math.random() * 5 + 0);
    }
    componentDidCatch(error, info) {
        console.log(error, info);
        if(error) {
            // 如果有错误信息,就重新渲染一下组件,可能是更好的交互
            this.setState({
                isError: true
            });
        }
    }
    render() {
        return (
            <div>
                <Cat name={this.state.catName} />
                {!this.state.isError ?
                    <button onClick={this.handleChangeCatName.bind(this)}>点我给猫咪取新名字!</button> :
                    <p>不要奴才给我取名字了!</p>
                }
            </div>
        );
    }
}

(6) 渲染相关

  • render

额...这个不想写了,先睡觉吧~应该写了这么多个小例子也差不多了~可以动手试试哦!还不清楚的可以 Google 一下,你就知道。

生命周期很重要,其实学到这里也差不多可以上手写点项目熟练一下了,其他的更多是思维和编程方面的东西,周期的篇幅单独来一篇吧~其他主题之后再继续吧!

没有评论:

发表评论