主要记录React的简单使用,便于查阅。
- 引入react库
- 组件(Components)
- 状态(State)
- 生命周期
一、引入react库
三种引入方式,分别如下:
- 用<script>标签加载react文件
- 在npm下使用ES6(推荐)
有node环境,如果没有node环境,先安装nodejs,在nodejs官网https://nodejs.org/en/下载需要的版本,进行安装。
然后在项目中安装react,npm install react --save-dev
最后在文件中引入react,import React from 'react' - 在npm下使用ES5
同样需要node环境,安装react,同上
最后在文件中引入react,var React = require('react')
引入react后,就会有React这个全局变量,React是React库的入口,顶级API都在React这个全局变量上,接下来就就可以使用React的API编写代码了。
二、组件(Components)
组件可以将用户界面分成独立的,可复用的小部件,并且可以对每个部件进行单独的设计。
React可以将组件定义为类或函数,定义为类组件会提供更多功能,所以常定义为类组件,要定义组件类,需要扩展React.Component,如下:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
注:必须在React.Component子类中定义render()方法。
三、状态(State)和生命周期
React把组件看成是一个状态机,通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致。
状态是私有的,完全受控于当前组件。
React里,只需要更新组件的state,然后根据新的state重新渲染用户界面(不用操作DOM)。
如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
如何定义State
State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。
组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:
- 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
- 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
- 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
- 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。
- 注意:不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。
如何使用state
1、 在构造函数(constructor)中初始化state
2、在render中使用state的值
3、用setState修改state,从而触发DOM重新渲染
注意:不要直接修改state,这样并不会重新触发render,如:this.state.date = '2018年7月8日'
用setState修改,如:this.setState({date: ' 2018年7月8日'})
4、 setState方法是异步的
调用setState时,组件state不会立即改变,只是把要修改的状态放入事件队列中,react会优化真正的执行时机,并且处于本身的性能原因,可能会对此setState的状态修改合并成一次状态修改。
因此不要依靠当前的state计算下一个state,因为当真正执行状态修改时,依赖的this.state并不能保证是最新的state,因为react本身会把多次state合并成一次,这时this.state可能还是setState前的state。
如果需要使用当前state计算新的state,正确用法:
//preState: 当前的最新状态的前一个状态
//props: 当前最新的props
this.setState((preState,props)=>({counter:preState.quantity+1}))
如果要在setState方法后直接获取更新后的state值,需要在setState中传入第二个参数,传入一个回调函数,正确写法为:
this.setState({date: ' 2018年7月8日'}, () => {console.log(this.state.date)})
5、state的更新是一个合并的过程
当调用setState()修改组件状态时,只需要传入发生变化的state,不是完整的state,因为组件state的更新是一个合并的过程,如:一个组件的状态为:
this.state({
title: 'React',
content: 'React is an wondeful JS library'
})
当只需要修改title时,只需要将修改的title传给setState即可:
this.setState({title: 'ReactJs'})
react会合并最新的title到原来的状态,同时保留原来的content。
6、state与不可变对象
React官方把state当成不可变对象,一方面是直接修改this.state组件不会重新render,通过setState修改state才会render;另一方面,state中包含的所有状态都应该是不可变对象。
当state当中的某一个状态发生变化时,应该重新创建这个状态对象,不是直接修改原来的state状态。
状态类型可以分为三种情况:
- 状态类型为不可变类型(number, string, boolean, null, undefined)
状态为不可变类型时,直接给要修改的状态赋新值即可。 - 状态类型为数组
假如有一个数组类型的状态books,当想给books中增加一本书时,可以使用数组的concat方法或者es6的扩展运算符
//方法一:使用preState,concat创建新数组
this.setState((preState) => ({
books: preState.books.concat(['React'])
}))
//方法二:使用ES6扩展运算符
this.setState(preState => ({
books: [...preState, 'React']
}))
当从books中截取部分元素作为新状态时,可以使用数组的slice方法:
this.setState(preState => ({
books: preState.books.slice(1, 3)
}))
当从books中过滤部分元素作为新状态时,可以使用filter方法:
this.setState(preState => ({
books: preState.books.filter(item => item !== 'React')
}))
- 状态的类型是普通对象
//使用es6的Object.assign()方法
this.setState(preState => {
onwer: Object.assign({}, preState.onwer, {name: 'Jason'})
})
//使用对象扩展运算符
this.setState(preState => {
owner: {...preState.owner, name: ''Jason}
})
注意:创建新的状态对象关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。可以使用一些Immutable的JS库,如Immutable.js,实现类似的效果。
为什么React推荐组件的状态是不可变对象?
一方面是因为不可变对象方便管理和调试,另一方面是出于性能考虑,当对象组件状态都是不可变对象时,在组件的shouldComponentUpdate方法中,仅需要比较状态的引用就可以判断状态是否真的改变,从而避免不必要的render调用
四、生命周期
React定义了组件的生命周期,主要分为三个过程:
- 挂载过程(Mount),组件第一次在DOM树渲染的过程
- 更新过程(Update),组件被重新渲染的过程
- 卸载过程(Unmount),组件从DOM树中删除的过程
执行这3个过程的调用函数就是声明周期函数。
挂载过程
该过程会依次调用如下函数:
- constructor():ES6类的构造函数,接收两个参数props, context,可以获得父组件传下来的props(为了初始化state和绑定this)
- componentWillMount():在组件被挂载前调用,只执行一次
- render():渲染组件
- componentDidMount():组件挂载后调用,这时已经生成了真实的DOM节点,只执行一次,一般在这里发送异步请求,操作DOM
更新过程
当组件的props或者state改变时会触发组件的更新过程
该过程会依次执行如下函数:
- componentWillReceiveProps(nextProps):在接受父组件改变后的props需要重新渲染组件时用到的比较多,初始化时不调用
- shouldComponentUpdate(nextProps,nextState):判断组件是否应该重新渲染,默认是true,当props或者state改变时调用(父组件重新渲染),初始化时不调用,返回boolean。true表示继续执行render方法,false表示跳过本次渲染。在这里可以优化react性能。
- componentWillUpdate(nextProps,nextState):shouldComponentUpdate返回true后,componentWillUpdate会被调用
卸载过程
componentWillUnmount:将组件从DOM树移除,防止内存溢出
测试生命周期执行顺序:
class App extends React.Component {
constructor(props){
super(props)
console.log("---初始化组件---")
}
componentWillMount(){
console.log("---组件挂载前---")
}
componentDidMount(){
console.log("---组件挂载后---")
}
componentWillReceiveProps(nextProps){
console.log("---父组件重新渲染---")
}
shouldComponentUpdate(nextProps,nextState){
console.log("---组件接受到重绘状态---")
if(this.props != nextProps || this.state != nextState)
return true
}
render() {
console.log("---组件渲染---")
}
}
参考文档:
http://www.css88.com/react/docs/getting-started.html
https://www.jianshu.com/p/c6257cbef1b1