react 生命周期详解

React-ES6 :

class App extends React.Component {
	static get propTypes() {
		return {
			//nameProp: PropTypes.number.isRequired,
		}
	}
	static get defaultProps() {
		return {
			nameProp: '初始化props'
		};
	}
	constructor(props, context) {
		super(props);
		this.state = {
			data: ["初始化state"]
		};
	}

	//组件即将插入到真实的dom节点中
	componentWillMount() {
		//ajax...
		console.log('组件即将插入到真实的dom节点中');
	}
	//组件已经插入到真实的dom节点中
	componentDidMount() {
		//ReactDOM.findDOMNode(this.refs)
		console.log('组件已经插入到真实的dom节点中');
	}
	//组件将要接收到新属性
	componnentWillReceiveProps(nextProps) {
		console.log('组件将要接收到新属性');
	}
	//组件是否要被重新渲染:return true?渲染(默认):不渲染
	shouldComponentUpdate(nextProps, nextState) {
		console.log('组件是否要被重新渲染:return true?渲染(默认):不渲染');
		return true;
	}
	//组件即将要被重新渲染
	componentWillUpdate(nextProps, nextState) {
		console.log('组件即将要被重新渲染');
	}
	//组件已经被重新渲染
	componentDidUpdate(nextProps, nextState) {
		console.log('组件已经被重新渲染');
	}

	render() {
		const {count} = this.props;
		console.log("this.props.children");
		console.log(this.props.children);
		return(
			<div className={styles.root}>
                <HeaderBar/>
                <div >
                    <TabBar count={count}/>
                    <div >
                        {this.props.children}
                    </div>
                </div>
            </div>
		)
	}

	//组件即将移除真实的dom节点
	componentWillUnmount() {
		//清除定时器,延迟操作,移除事件等 setInterval,setTimeout
		console.log('组件即将移除真实的dom节点');
	}
}
复制代码

react:j基础:www.cnblogs.com/libin-1/p/6…

React组成部分:属性,状态和生命周期!

React的生命周期对组件而言是一个很重要的存在,每一个状态都有其重要的作用。

第一次Redner

首次渲染执行的顺序如图上

getDefaultProps相当于ES6中staticdefaultProps = {}
getInitialState相当于constructor中的 this.state = {}
复制代码

Props 改变

State 改变

组件卸载

自定义组件的3个阶段

自定义组件的主要通过3个阶段来管理,分别是MOUNTINGRECEIVE_PROPSUNMOUNTING,3个阶段对应3中方法,分别是:mountComponent、updateComponent 和 unmountComponent。

  1. 其中will前缀的方法是进入状态之前的调用,比如componentWillReceiveProps,此方法中改变state不会二次渲染而是进行state合并,并且只有在componentDidUpdate后才能获取更新后的this.state。如果想获取组件默认的props,并且赋值给State ,就可以在这里修改,达到UI上的效果。
  2. did的前缀表示进入状态之后调用,比如componentDidMount,组件一般初始化都会在这里进行数据请求。
    componentDidMount() {
        this.gitInitData()
    }复制代码

shouldComponentUpdate 是在第二阶段RECEIVE_PROPS运行的,从

1. componentWillReceivePorps 

2. shouldComponentUpdate 

3. componentwillUpdate 

4. render 

5. componentDidUpdate
复制代码

shouldComponentUpdate接收需要更新的props 和 state, 如果方法返回false的时候就不会向下执行生命周期了。(默认情况下React会渲染所有节点,所以返回true),react性能优化的主要手段之一。

注意: 在shouldComponentUpdate和componentwillUpdate中切勿使用setState方法,会导致循环调用。

第三阶段:UNMOUNTING

unmountComponent负责组件中的componentWillUnmount 如果存在componentWillUnmount,则执行重置所有相关参数。在该方法中调用setState不会触发render,因为所有的更新队列,更新状态都被重置为null。

无状态组件

在React开发中,我们需要一些单纯的只接受props进行渲染的组件,更易于管理,简单开发。无状态组件只是一个render方法,没有存在实例返回,接收props渲染成DOM结构。在项目开发中作为view组件的存在,应该尽量的多使用无状态组件。 栗子

const Pane = (props) => <div>{props.children}</div>;

Pane.propTypes = {
  ...
};复制代码

在我们平常的组件开发中,组件可以分为数据层,渲染层。其中单纯的渲染层就可以是无状态组件。


github.com/asd0102433/…










创建虚拟dom

  由react对象提供的一个方法createElement

  第一个参数表示虚拟dom的名称,例如div

    有时我们还可以传递组件

  第二个参数是一个对象,表示虚拟dom中拥有的属性

    从第三个参数开始表示该虚拟dom元素的子元素

  子元素也要由createElement创建,但是文本节点可以直接写

  方法的返回值是一个虚拟dom(js对象)

Render

  由ReactDOM提供的一个方法

    三个参数

      1 渲染虚拟dom元素

      2 真实的dom元素

      3 渲染完成回调函数(不常用)

1// 创建虚拟dom
2var h1 = React.createElement(
3        'h1', 
4        {
5            title: '这是标题'
6        }, 
7        '我是文本内容啦啦啦'
8    )
9// 将h1渲染到页面中
10ReactDOM.render(h1, document.getElementById('app'), function () {
11    console.log(arguments)
12    console.log(this)
})复制代码

组件

  在react中定义的一个虚拟dom很难复用,所以通常我们将一组虚拟dom定义在组件中来复用

    createClass可以用来创建一个组件

  参数是一个对象,用来描述组件的

  可以在对象中定义render方法,通过返回值来渲染这组组件

  返回值,通常所有虚拟dom都在一个容器内

  组件式一个类,因此组件名称要以大写字母开头

  组件要想渲染到页面中,就要将组件转化成虚拟dom,通过React.createElement方法(由React-dom.js提供)

1var List = React.createClass({
2    // 通过render渲染页面
3    render: function () {
4        return (
5             React.createElement(
6                'ul', 
7                null, 
8                React.createElement('li', null, '六间房秀场'),
9                React.createElement('li', null, '斗鱼TV'),
10                React.createElement('li', null, '美女秀场'),
11                React.createElement('li', null, '秀色直播')
12            )
13        )
14    }
15})
16// 将组件渲染到页面中
17// 转化组件到虚拟DOM
18var ul = React.createElement(List)
ReactDOM.render(ul, document.getElementById('app'))复制代码

JSX语法

  我们写虚拟dom的最大问题,创建一个虚拟dom成本太高了(写的太麻烦了),React团队为了简化对createElement的书写,为我们提供了jsx语法

    react团队提供了两种处理方法

      第一种,在浏览器端编译

          引入编译库,例如browser.js可以对jsx语法编译

          此时定义的script标签的type类型要定义成text/babel, 在里面可以写jsx语法

      第二种,在工程化中编译(最常见的)

          编译jsx语法,跟以前编译less,sass,stylus很像

          首先要获取这些jsx文件(通常我们将写jsx语法的文件拓展名改成.jsx)

  以fis为例

1fis.match('**.jsx', {
2    // 编译
3    parser: 'babel2',
4    // 更改后缀名称
5    rExt: '.js'
})复制代码

特殊属性

Class

  Class在js中是保留字,因此在react定义虚拟dom的时候,将class写成className

For (是label元素特有的属性)

  For是js中的关键字,因此在react定义虚拟dom的时候,将for属性写成htmlFor

1var h1 = (<h1 className="red">我是文本内容啦啦啦</h1>);
2// 将虚拟dom渲染到页面中
3ReactDOM.render(h1, document.getElementById('app'))
4
5// 创建一个组件
6var User = React.createClass({
7    render: function() {
8        // 返回虚拟dom
9        return (
10            <div>
11                <label htmlFor="user_name">用户名</label>
12                <input id="user_name" type="text" />
13            </div>
14        );
15    }
}) 复制代码

插值

  React支持插值语法,我们在jsx语法中使用,语法是 {}

  一对{}提供了一个js环境,因此我们可以在大括号里面设置虚拟dom元素的属性,设置虚拟dom元素的内容

  我们可以在插值符号中使用js中的任何表达式

非元素属性

  Key 为列表元素定义react-id,绑定id。这样可以方便获取页面中哪些元素更新了,哪些元素需要更新

  Render方法的作用域是组件实例化对象,可以访问到组件中定义的方法

1createChildList: function () {
2    // 遍历数组,处理每一个成员,然后映射一个新数组,就是map方法
3    return data.map(function (value, index) {
4        // 每一个li要绑定内容,还要设置key
5        return <li key={index}>{value}</li>;
6    })
},复制代码

属性

  在html中,对于同一类元素来说,之所以展现的样式不一样,是因为他们具有不同的属性,所以属性可以让同一类元素展现出不同的状态

  同样的道理,在react中,对于同一个组件来说,可以创建一组虚拟dom树,如果想让虚拟dom树展现出不同的状态,我们就要为其添加属性

  在虚拟dom上添加属性跟在html中元素上添加属性是一样的,通过添加一个属性实现(只不过在组件上添加的都是自定义属性)

  我们添加的自定义属性,会存储在组件的props属性中,我们通过this.props可以访问到里面的数据

  组件的默认属性我们定义在getDefaultProps中,通过返回值设置默认属性数据

1// 创建导航标题组件
2var Nav = React.createClass({
3    // 定义默认属性数据
4    getDefaultProps: function () {
5        // 通过返回值定义默认属性数据
6        return {
7            data: ['默认标题']
8        }
9    },
10    // 封装渲染内容的方法
11    createChildList: function () {
12        var me = this;
13        // 遍历this.props.data渲染
14        return this.props.data.map(function (value, index) {
15            return (<a href="" key={index}>{value}{index != me.props.data.length - 1 ? '/' : ''}</a>)
16        })
17    },
18    // 通过render方法渲染虚拟dom树
19    render: function () {
20        return (
21            <div>
22                {this.createChildList()}
23            </div>
24        )
25    }
26})
27var data1 = ['财经', '证券', '理财'];
28// 渲染
ReactDOM.render(<Nav data={data1} />, document.getElementById('app'))复制代码

样式

  在虚拟dom中我们可以为元素定义样式

  在react中,虚拟dom上不能使用行内样式字符串,行内样式只能定义成对象,Css属性名称如果出现多个单词,要使用驼峰式命名,例如

    border-color => borderColor

  还要求浏览器前缀第一个字母要大写,例如

    -webkit-box-shadow => WebkitBoxShadow

  在createElement方法中,样式写在style中,直接赋值对象,在jsx语法中,样式写在style中,要使用插值语法

1// 定义虚拟dom
2var h1 = React.createElement('h1', {
3    style: {
4        color: 'red',
5        fontSize: '40px'
6    }
7}, '我是文本内容啦啦啦');
8
9// jsx语法,定义虚拟dom
10var h1 = (<h1 style={{
11    color: 'green',
12    fontSize: '100px'
}}>文本内容</h1>)复制代码

事件

  React中定义事件,跟在html中定义事件很像

  在html中定义事件

    <button οnclick="console.log('hello')">按钮</button>

  在react中jsx语法中定义事件,跟html中定义事件很像

    <button onClick={this.clickBtn}>按钮</button>

  on+事件名称=回调函数
    事件名称首字母大写

    事件回调函数通常绑定组件中的方法

    事件回调函数不要执行(后面不要加())

  事件回调函数

    作用域是组件实例化对象(可以通过this访问组件上的方法以及属性数据)

    可以通过bind方法更改作用域

    可以通过bind方法传递自定义参数(很少用)

  参数有三个

    React封装的事件对象(最常用)

    React-id

    源生的事件对象

1var Demo = React.createClass({
2    // 定义事件回调函数
3    clickBtn: function () {
4        console.log(arguments)
5        console.log(this)
6    },
7    render: function () {
8        return (
9            <div>
10                <button onClick={this.clickBtn.bind(this, 11)}>这是个按钮</button>
11            </div>
12        )
13    }
}) 复制代码

这个就是参数

状态

  状态跟属性一样都是在组件内部存储数据的

    属性是组件外部传递的数据

    状态是组件内部维护的数据

      有状态组件以及无状态组件

        无状态组件

          对于一个组件来说,如果组件没有状态,也就是说组件式一成不变的,组件在创建之后,不会发生交互,请求数据等等,这类组件叫无状态组件,

          组件自身不会维护状态

        有状态组件

          对于一个组件来说,自从创建以后,组件会产生一些交互,请求一些数据,来完成自身状态的更新,这类组件内部必须维护一个状态来存储这些变化的数据,这类组件叫有状态

    组件处于哪种状态由其自身存储的数据决定,组件的存储跟属性一样,在组件实例化对象中有个state属性,就是用来存储状态数据

    初始化状态用getInitialState方法定义,通过return 将初始化状态的数据返回

    修改状态,用setState方法

      参数是一个对象,对象中的属性就是即将修改的状态

    状态或者属性的改变都会触发render方法的执行,这句话很重要

最后是一个小小的换肤案例

  html代码

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" type="text/css" href="less/15.less">
</head>
<body>
    <div id="app"></div>
<script type="text/javascript" src=""></script>
<script type="text/javascript" src=""></script>
<script type="text/javascript" src=""></script>
<script type="text/javascript" src=""></script>
</body>
</html>复制代码

  less代码

* {
    list-style: none;
    margin: 0;
    padding: 0;
}
body {
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-position: center 0;
    background-size: cover;
}
#app {
    width: 1118px;
    margin: 50px auto;
    ul {
        margin-right: -10px;
    }
    li {
        width: 178px;
        margin-right: 10px;
        float: left;
        margin-bottom: 10px;
    }
    p {
        line-height: 30px;
        text-align: center;
    }
    img {
        cursor: pointer;
    }
}复制代码

  jsx代码

// 定义换肤组件
var Skin = React.createClass({
    // 初始化状态数据
    getInitialState: function () {
        return {
            list: []
        }
    },
    // 定义事件回调函数
    chooseImage: function (e) {
        // 获取li元素
        var li = e.currentTarget;
        // 获取
        var id = li.getAttribute('data-id')
        // 用id获取大图片的地址,渲染body
        document.body.style.backgroundImage = 'url(img/skin/big_' + id + '.jpg)'
        // console.log(id)
    },
    // 定义渲染列表的方法
    getImageList: function () {
        var me = this;
        // 通过状态来渲染了
        return this.state.list.map(function (obj, index) {
            return (<li key={index} data-id={obj.id} onClick={me.chooseImage}>
                    <img src={"img/skin/" + obj.src} alt=""/>
                    <p>{obj.title}</p>
                </li>)
        })
    },
    render: function () {
        return (
            <ul>{this.getImageList()}</ul>
        )
    },
    // 发送请求获取数据
    componentDidMount: function () {
        var me = this;
        $.get('data/skin.json', function (res) {
            // 请求成功,更新状态数据
            if (res && res.errno === 0) {
                me.setState({
                    list: res.data
                })
            }
        })
    }
})    

// 渲染到页面中
ReactDOM.render(<Skin />, document.getElementById('app'))复制代码



生命周期

  React中的组件被看成是一个有生命的个体,因此赋予了声明周期的概念,就是为了定义组件所处的状态

    React中组件共分三大周期,11个阶段

      创建期(少年,成长)组件创建时候进入的时期

        getDefaultProps getInitialState componentWillDid render componentDidMount

      存在期(中年,反反复复的工作)组件内部数据(属性或者状态)更新时候进入的时期

        componentWillReceiverProps shouldComponentUpdate componentWillUpdate render componentDidUpdate

      销毁期(老年,消亡)组件从页面中删除时候进入的时期     

        componentWillUnmount
  

  下面我们来一一细说每个时期  

  创建期

    创建期是组件创建时候进入的时期,共分成五大阶段

    一旦组件创建完成,创建期就结束,就进入下个周期 --> 存在期

     1 创建默认属性数据 --> getDefaultProps

        没有参数

        作用域是构造函数,说明组件构造函数还没有执行完毕,所以不能访问组件实例化对象

        访问不了组件中任何的数据,方法

        返回值是默认的属性数据

     2 初始化状态数据 --> getInitialState

        没有参数

        作用域是组件实例化对象

        所以在这个方法中,可以访问属性数据了,但是不能访问状态数据

        所以工作中,我们通常在这个方法中用属性的数据,初始化状态数据

        这样就实现了将外部的数据,传递给内部更新维护

        返回值是初始化的状态数据

     3 组件即将被创建 --> componentWillMount

        没有参数

        作用域是组件实例化对象

        在这个阶段,我们可以获取属性数据,可以获取状态数据,就是不能获取dom元素

        所以工作中通常会简单处理属性数据,或者状态数据,或者处理内部的方法,但是绝对不能更新属性数据或者状态数据,也不要为组件绑定元素绑定事件

     4 渲染输出组件 --> render

        没有参数
        作用域是组件实例化对象

        不能获取dom元素(这个方法执行完毕才能获取),可以获取属性数据,可以获取状态数据

        返回值是渲染输出的虚拟dom元素

     5 组件创建完成 --> componentDidMount

        没有参数

        作用域是组件实例化对象

        工作中很常用

        可以访问属性数据,可以访问状态数据,可以访问dom元素

        还可以在这里发送异步请求,请求数据更新状态

        还可以绑定事件

        还可以更新状态

    这个方法执行完毕就标志着组件创建期的结束

    获取组件对应的真实dom元素

      0.13版本之前

        this.getDOMNode()

        React.findDOMNode(this)

        都可以获取

      0.13版本之后

        只能使用ReactDOM.findDOMNode(this)

    

1var GoTop = React.createClass({
2    // 第一个阶段 初始化默认属性
3    getDefaultProps: function () {
4        console.log(111, this)
5        console.log(111, arguments)
6        return {
7            text: '分类网址'
8        }
9    },
10    // 第二个阶段 初始化状态
11    getInitialState: function () {
12        console.log(222, this)
13        console.log(222, arguments)
14        // console.log(this.state)
15        return {
16            color: 'red',
17            // 用属性数据更新状态
18            text: this.props.text
19        }
20    },
21    // 第三个阶段 组件即将构建
22    componentWillMount: function () {
23        console.log(333, this)
24        console.log(333, arguments)
25        // 获取虚拟dom对应的真实的dom元素
26        // console.log(ReactDOM.findDOMNode(this))
27        // console.log(this.props)
28        // console.log(this.state)
29    },
30    // 第四个阶段 渲染组件
31    render: function () {
32        console.log(444, this)
33        console.log(444, arguments)
34        // console.log(ReactDOM.findDOMNode(this))
35        // 渲染输出虚拟dom
36        return (
37            <div className="go-top">
38                {/*这里的文案实时改变,需要在内部围护,因此要使用状态数据*/}
39                <span onClick={this.goTop}>{this.state.text}</span>
40            </div>
41        )
42    },
43    // 返回顶部的方法
44    goTop: function () {
45        if (this.state.text === '返回顶部') {
46            window.scrollTo(0, 0)
47        }
48    },
49    // 第五个阶段 组件构建完成
50    componentDidMount: function () {
51        console.log(555, this)
52        console.log(555, arguments)
53        // 获取虚拟dom对应的真实的dom元素
54        // console.log(ReactDOM.findDOMNode(this))
55        // react以及组件实例化对象上的不建议使用了
56        // console.log(React.findDOMNode(this))
57        // console.log(this.getDOMNode())
58
59        // 事件在最后一个阶段绑定
60        window.onscroll = function () {
61            // console.log(111)
62            // 滚动200更改文案
63            if (window.scrollY > 200) {
64                this.setState({
65                    text: '返回顶部'
66                })
67            } else {
68                this.setState({
69                    text: '分类网址'
70                })
71            }
72        }.bind(this)
73    }
})复制代码

在说下一个生命周期之前,插入一个小知识点

  子组件 

     在组件中使用的组件,叫该组件的子组件,在组件中使用子组件很简单,跟使用虚拟DOM元素一模一样

<Slider>
    <GoTop color=“red” />
</Slider>复制代码

    这里定义的GoTop组件就叫Slider组件的子组件

    在组件中可以为子组件添加属性,添加的属性就是对子组件传递的数据

    传递的数据分成四类

      1 如果属性值是一个变量(字符串,对象等)

        传递给子组件的数据不会改变

      2 如果属性值是父组件中的属性数据

        传递给子组件中的数据往往不会改变(除非父组件也是一个子组件)(因为组件的属性数据只能在外部改变,如果父组件是其他组件的子组件,父组件的属性数据可能会改变,此时传递给子组件的数 据就可能发生改变)

      3 如果属性值是父组件中的状态数据

        如果父组件中状态数据发生了改变,那么传递给子组件的数据就发生了改变

      4 如果属性值是父组件中的方法

        父组件提供的方法作为子组件的事件回调函数,作用域仍然是父组件

        回调函数两个参数

          第一个是react封装的事件对象

          第二个是源生的事件对象

        如果父组件提供的方法在子组件的方法内执行,没有参数了,但作用域依旧是父组件

          (搞不清楚的童鞋们请注意观察下列代码的第26行和29行)

      这里的数据传递,父组件将数据传递给子组件,本质上就是父组件向子组件通信

  

 1 // 创建GoTop组件
 2 var GoTop = React.createClass({
 3     // 定义默认状态数据
 4     getDefaultProps: function () {
 5         return {
 6             text: '网址导航'
 7         }
 8     },
 9     // 定义初始化状态
10     getInitialState: function () {
11         return {
12             // 用属性数据赋值状态数据
13             text: this.props.text
14         }    
15     },
16     // 返回顶部的方法
17     goTop: function () {
18         // console.log(111,arguments)
19         // console.log(222,this);
20         // 在子组件的方法中使用父组件提供的方法
21         this.props.goBackByParent()
22     },
23     render: function () {
24         console.log(this)
25         // 渲染输出虚拟DOM
26         // <span onClick={this.props.goBackByParent}>{this.props.text}</span>
27         return (
28             <div className="go-top">
29                 <span onClick={this.goTop}>{this.props.text}</span>
30             </div>
31         )
32     }
33 })
34 
35 // 定义slider组件
36 var Slider = React.createClass({
37     // 定义属性
38     getDefaultProps: function () {
39         return {
40             title: '头条新闻'
41         }
42     },
43     // 定义状态数据
44     getInitialState: function () {
45         return {
46             text: '新闻头条'
47         }
48     },
49     // 渲染输出虚拟dom
50     render: function () {
51         return (
52             <div className="slider">
53                 {/*GoTop 就是Slider的子组件*/}
54                 <GoTop color="red" title={this.props.title} text={this.state.text} goBackByParent={this.goBack} />
55             </div>
56         )
57     },
58     // 定义返回顶部方法
59     goBack: function () {
60         console.log(123, this, arguments)
61     },
62     // 定义滚动事件
63     componentDidMount: function () {
64         window.onscroll = function () {
65             if (window.scrollY > 200) {
66                 this.setState({
67                     text: '返回顶部'
68                 })
69             } else {
70                 this.setState({
71                     text: '网址导航'
72                 })
73             }
74         }.bind(this)
75     }
76 })
77 
78 // 将组件渲染到页面中
79 ReactDOM.render(<Slider />, document.getElementById('app'))复制代码

存在期

  存在期也有五个阶段

  这五个阶段方法的作用域都是组件实例化对象,因此这个五个阶段都能访问组件中的属性和状态,当然还有虚拟DOM

  6 组件即将接收新的属性 --> componentWillReceiveProps

    有一个参数表示新的属性对象

    工作中经常在这个阶段用新的属性对象,更新状态数据

    这个方法只有在属性更新的时候,会执行,状态更新的时候不会执行

  7 组件是否应该更新 --> shouldComponentUpdate

    有两个参数

      第一个参数表示新的属性

      第二个参数表示新的状态

    通过作用域可以访问原来的属性,原来的状态,原来的dom

    必须有返回值

      True表示可以继续更新

      False表示不能继续更新

    我们可以通过设置false属性来优化组件更新的频率

  8 组件即将更新 --> componentWillUpdate

    有两个参数

      第一个参数表示新的属性

      第二个参数表示新的状态

    通过作用域可以访问原来的属性,原来的状态,原来的dom

    在这个阶段,一定不要再更新属性或者状态了

    属性或者状态数据,在这个阶段更新完毕的

  9 组件即将渲染输出新的虚拟DOM --> Render(这里的render方法跟创建期是同一个)

    通过作用域可以访问新的的属性,新的的状态,原来的dom

    在这里绝对不能操作原来的dom,因为在创建期也使用这个方法(在创建期的render方法中是不能访问dom的)

  10 组件更新完毕 --> componentDidUpdate

    有两个参数

      第一个表示原来的属性对象

      第二个表示原来的状态对象

    是最后一次可以访问到原来的属性数据或者状态数据了

    通过作用域可以访问新的的属性,新的的状态,新来的dom

    这个方法执行完毕,组件存在期就完成一次更新,我们可以在这个方法中再次更新属性,再次更新状态,发送请求,绑定事件等等

    当组件中属性数据或者状态数据发生了改变的时候,会进入该周期

  1 // 创建GoTop组件
  2 var GoTop = React.createClass({
  3     // 定义默认状态数据
  4     getDefaultProps: function () {
  5         return {
  6             text: '网址导航'
  7         }
  8     },
  9     // 定义初始化状态
 10     getInitialState: function () {
 11         return {
 12             // 用属性数据赋值状态数据
 13             text: this.props.text
 14         }    
 15     },
 16     // 返回顶部的方法
 17     goTop: function () {
 18         console.log(111)
 19     },
 20     render: function () {
 21         console.log(4, this.state.text)
 22         console.log(4, arguments)
 23         // 渲染输出虚拟DOM
 24         return (
 25             <div className="go-top">
 26                 <span onClick={this.props.goBackByParent}>{this.state.text}</span>
 27             </div>
 28         )
 29     },
 30     // 更新状态
 31     componentDidMount: function () {
 32         // var me = this;
 33         // setTimeout(function () {
 34         //     me.setState({
 35         //         text: '返回顶部'
 36         //     })
 37         // }, 1000)
 38         window.onscroll = function () {
 39             // console.log(111)
 40             // 滚动200更改文案
 41             if (window.scrollY > 200) {
 42                 this.setState({
 43                     text: '返回顶部'
 44                 })
 45             } else {
 46                 this.setState({
 47                     text: '分类网址'
 48                 })
 49             }
 50         }.bind(this)
 51     },
 52     // 第一个阶段是 组件即将接收新的属性
 53     componentWillReceiveProps: function(newProps) {
 54         console.log(1, this)
 55         console.log(1, arguments)
 56         // 用属性数据更新状态
 57         this.setState({
 58             text: newProps.text
 59         })
 60     },
 61     // 第二个阶段 组件是否应该更新
 62     shouldComponentUpdate: function (newProps, newState) {
 63         console.log(2, this)
 64         console.log(2, arguments)
 65         console.log(ReactDOM.findDOMNode(this).innerHTML)
 66         // 组件状态text值更新时候,进入存在期,
 67         // 判断新的text值与旧的text值的变化
 68         if (newState.text !== this.state.text) {
 69             return true;
 70         } else {
 71             return false
 72         }
 73         // return false
 74     },
 75     // 第三个阶段 组件即将更新
 76     componentWillUpdate: function() {
 77         console.log(3, this.state.text)
 78         console.log(3, arguments)
 79     },
 80     // 第五个阶段 组件更新完毕
 81     componentDidUpdate: function() {
 82         console.log(5, this)
 83         console.log(5, arguments)
 84         console.log(ReactDOM.findDOMNode(this).innerHTML)
 85 
 86     }
 87 })
 88 
 89 // 定义slider组件
 90 var Slider = React.createClass({
 91     // 定义属性
 92     getDefaultProps: function () {
 93         return {
 94             title: '头条新闻'
 95         }
 96     },
 97     // 定义状态数据
 98     getInitialState: function () {
 99         return {
100             text: '新闻头条'
101         }
102     },
103     // 渲染输出虚拟dom
104     render: function () {
105         return (
106             <div className="slider">
107                 {/*GoTop 就是Slider的子组件*/}
108                 <GoTop color="red" title={this.props.title} text={this.state.text} goBackByParent={this.goBack} />
109             </div>
110         )
111     },
112     // 定义返回顶部方法
113     goBack: function () {
114         console.log(123, this, arguments)
115     },
116     // 定义滚动事件
117     componentDidMount: function () {
118         
119     }
120 })
121 
122 // 将组件渲染到页面中
123 ReactDOM.render(<Slider />, document.getElementById('app'))复制代码

 销毁期

  销毁期:当组件从页面中删除,组件就进入了销毁期

    11 销毁期只有一个阶段 --> componentWillUnmount

      没有参数

      作用域是组件实例化对象

    这个方法是能够访问组件的最后一次机会了,这个方法执行完毕之后,组件就销毁了

 1 // 定义返回顶部组件
 2 var GoBack = React.createClass({
 3     render: function () {
 4         return (
 5             <div className="go-back">
 6                 <span>返回顶部</span>
 7             </div>
 8         )
 9     },
10     // 定义组件销毁期的阶段
11     componentWillUnmount: function () {
12         console.log(this)
13         console.log(arguments)
14     }
15 })
16 
17 // 将组件渲染到页面中
18 ReactDOM.render(<GoBack />, document.getElementById('app'))
19 
20 // 3秒钟之后渲染h1
21 setTimeout(function () {
22     ReactDOM.render(<h1>爱创课堂</h1>, document.getElementById('app'))
23 }, 3000)复制代码



深入Redux架构

阅读目录

回到顶部

关于redux

之前写了一篇通过一个demo了解Redux,但对于redux的核心方法没有进行深入剖析,在此重新总结学习,完整的代码看这里。(参考了React 技术栈系列教程)

什么情况需要用redux?

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。多交互、多数据源场景就比较适合使用Redux。

设计思想:

  • Web 应用是一个状态机,视图与状态是一一对应的。
  • 所有的状态,保存在一个对象里面。

Redux工作流程:

首先,用户发出 Action。

store.dispatch(action);复制代码

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);复制代码

State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener);复制代码

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}复制代码

如果现在没理解以上流程,不要急,看完以下API就差不多能懂得Redux的核心机制了。

回到顶部

API

Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

下面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。

import { createStore } from 'redux';
const store = createStore(fn);复制代码

State

Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过store.getState()拿到。

import { createStore } from 'redux';
const store = createStore(fn);

const state = store.getState();复制代码

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

Action

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};复制代码

上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux

可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');复制代码

store.dispatch()

store.dispatch()是 View 发出 Action 的唯一方法。

import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});复制代码

上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo('Learn Redux'));复制代码

Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。下面是一个实际的例子

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
});复制代码

上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。

import { createStore } from 'redux';
const store = createStore(reducer);复制代码

上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

store.subscribe()

Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);复制代码

显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。

store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();复制代码

回到顶部

中间件与异步操作

一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。

怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。

为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。

中间件的用法

本文不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的redux-logger模块。这里只介绍怎么使用中间件。

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);复制代码

上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

这里有两点需要注意:

(1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);复制代码

(2)中间件的次序有讲究。

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);复制代码

上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。

回到顶部

异步操作的基本思路

理解了中间件以后,就可以处理异步操作了。

同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

  • 操作发起时的 Action
  • 操作成功时的 Action
  • 操作失败时的 Action

以向服务器取出数据为例,三种 Action 可以有两种不同的写法。

// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }复制代码

除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。

let state = {
  // ... 
  isFetching: true,
  didInvalidate: true,
  lastUpdated: 'xxxxxxx'
};复制代码

上面代码中,State 的属性isFetching表示是否在抓取数据。didInvalidate表示数据是否过时,lastUpdated表示上一次更新时间。

现在,整个异步操作的思路就很清楚了。

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

redux-thunk中间件

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

奥妙就在 Action Creator 之中。

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }

// ...复制代码

上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)。这里的fetchPosts就是 Action Creator。

下面就是fetchPosts的代码,关键之处就在里面。

const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
); 复制代码

上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。

上面代码中,有几个地方需要注意。

(1)fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。

(2)返回的函数的参数是dispatchgetState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。

(3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。

(4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。

这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数。

这时,就要使用中间件redux-thunk

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  reducer,
  applyMiddleware(thunk)
);复制代码

上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。

因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch

回到顶部

React-Redux的用法

为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。

这个库是可以选用的。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。

React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

UI组件

UI 组件有以下几个特征。

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 没有状态(即不使用this.state这个变量)
  • 所有数据都由参数(this.props)提供
  • 不使用任何 Redux 的 API

下面就是一个 UI 组件的例子。

const Title =
  value => <h1>{value}</h1>;复制代码

因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值。

容器组件

容器组件的特征恰恰相反。

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API

总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。

你可能会问,如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

connect()

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

connect方法的完整 API 如下。

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)复制代码

上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。connect方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

mapStateToProps

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。

作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。请看下面的例子。

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}复制代码

上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。

下面就是getVisibleTodos的一个例子,用来算出todos

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    default:
      throw new Error('Unknown filter: ' + filter)
  }
}复制代码

mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。

// 容器组件的代码
//    <FilterLink filter="SHOW_ALL">
//      All
//    </FilterLink>

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}复制代码

使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。

connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。

mapDispatchToProps()

mapDispatchToPropsconnect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

如果mapDispatchToProps是一个函数,会得到dispatchownProps(容器组件的props对象)两个参数。

const mapDispatchToProps = (
  dispatch,
  ownProps
) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter
      });
    }
  };
}复制代码

从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。

如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。

const mapDispatchToProps = {
  onClick: (filter) => {
    type: 'SET_VISIBILITY_FILTER',
    filter: filter
  };
}复制代码

<Provider>组件

connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。React-Redux 提供Provider组件,可以让容器组件拿到state

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)复制代码

上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。

React-Router路由库

使用React-Router的项目,与其他项目没有不同之处,也是使用ProviderRouter外面包一层,毕竟Provider的唯一功能就是传入store对象。

const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);复制代码


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值