参考资料
React简介
- 由faceBook在2013年5月开源推出
- 全新的函数式编程风格
- React 16版本之后称之为 React Fiber
- chrome插件调试React developer tools
这么称是因为在React 16版本之后,在底层在事件循环中加入了优先级这样的概念,可以在循环的碎片事件可以执行高优先级的交互。
React的开发环境搭建
- 引入.js文件来使用React
- 通过脚手架工具(如官方提供Create-react-app)来编码构建大型项目和目录。脚手架里的代码并不能直接运行,需脚手架编译之后才能识别运行,使用 Grunt、webpack等帮助编写脚手架。
npx create-react-app my-app
cd my-app
npm start
React 中的响应式设计思想和事件绑定
-
React在创建实例的时候, constructor(){} 是最先执行的
-
this.state 负责存储数据
-
如果修改state中的内容,不能直接改,需要通过setState进行修改
-
JSX中js表达式用{}包裹
-
事件绑定需要通过bind.(this)对函数的作用域进行变更
setState(异步)
异步初衷:假如连续三次更新state的数据,React会把三次setState合并成一次setState,只做一次虚拟DOM的比对,更新DOM。这样可以省去额外两次DOM比对带来的性能消耗。
this.setState(() => {
//更新state数据
},() => {
//更新state数据完成后执行
})
propTypes 和 defaultProps (参数类型的校验和默认值)
// React脚手架内置 这里直接import
import PropTypes from 'prop-types'
TodoItem.propTypes = { // 参数类型校验
test: PropTypes.string.isRequired, // 必传
item: PropTypes.string,
handleItemSDelete: PropTypes.func,
index: PropTypes.number
}
TodoItem.defaultProps = { // 若父组件没有传参数,可以指定默认值
test: 'defaultPropsValue'
}
Virtual DOM 与 Diff算法
传统页面渲染和更新(耗性能)
- 先定义state数据
- 模板
- 数据 + 模板结合,生成真实的DOM来显示
- state发生改变
- 数据 + 模板结合,生成真实的DOM来显示,替换原始的DOM
Virtual DOM渲染和更新(节省性能)
JSX ==>通过React.createElement方法 ==> Virtual DOM( JS对象 ) ==> 真实的DOM
优点:
- 提升性能
- 它使得跨段应用得以实现,如React Native
- state数据
this.state = { value: 'hello world' }
- JSX模版
render() {
return( <div id='abc'><span>{this.state.value}</span></div> )
}
==等价于不使用JSX模版的写法(不推荐)
render() {
return React.createElement("div", {
id: "abc"
}, React.createElement("span", null, this.state.value));
}
- state数据 + JSX模版结合生成 Virtual DOM( Virtual DOM就是一个JS对象,描述真实的DOM)
Virtual DOM :['div', {id: 'abc'}, ['span', {}, 'hello world']]
//虚拟DOM解释: 标签div,属性id='abc',子元素span,没有属性,值为hello world
- 用 Virtual DOM的结构生成真实的DOM显示出来
<div id='abc'><span>hello world</span></div>
- state数据发生变化
this.setState(() =>({ value: 'Bye bye' }))
- state数据 + JSX模版 生成新的 Virtual DOM
Virtual DOM :['div', {id: 'abc'}, ['span', {}, 'Bye bye']]
- 比较更新前的 Virtual DOM与更新后的 Virtual DOM( Diff算法 )
// 区别 hello world ==> Bye bye
Virtual DOM :['div', {id: 'abc'}, ['span', {}, 'hello world']]
Virtual DOM :['div', {id: 'abc'}, ['span', {}, 'Bye bye']]
// 然后直接操作DOM,修改span中的内容
Diff算法(同层比对)
两个虚拟DOM作比对,找到差异之后去更新真实DOM。首先比较最顶层DOM,假如一致,再去比较第二层,假如不一致(即使下面所有的DOM都一致),则停止比较,把原始虚拟DOM对应的节点DOM全部删除,重新生成新的DOM并替换原始DOM。
**** ****
key值
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中每一个元素赋予一个确定的标识。
假设有个数组list = ['a', 'b', 'c', 'd', 'e'],页面首次渲染的时候,映射成5个虚拟DOM节点,生成虚拟DOM树。
然后新增数据list = ['a', 'b', 'c', 'd', 'e', 'z'] ,state数据发生变化,生成新的虚拟DOM树。两个虚拟DOM树作比较。但如果两个虚拟DOM的节点没有key值,节点之间的关系很难确定,如下左图。
此时如果给每个节点设置key值。很容易就能发现区别,增加了z节点,如下右图。
所以也可以解释为什么循环列表的时候,不建议使用数组的index值作为key值。
list = ['a', 'b', 'c', 'd', 'e']
//index 0 1 2 3 4
list.splice(0,1)
list = ['b', 'c', 'd', 'e']
//index 0 1 2 3
// 事实上,list只是删除了字符串'a'
// 但是如果把index作为key值,则虚拟DOM对比的时候会发现虚拟DOM节点全部改变了。
ref的使用(尽量少用)
// JSX
<input onChange={this.handleInputChange.bind(this)} ref={(input) => {this.input = input}} />
// 或者 ref=‘input’
handleInputChange(e) {
console.log(e.target.value) // JSX中的input标签
console.log(this.input) // JSX中的input标签
// 或者this.refs.input
console.log(e.targe.value === this.input.value) // true
}
生命周期函数
组件一旦被创建constructor会被自动调用也可以理解为生命周期函数,但是属于ES6不归属于React
生命周期函数:是指在某一时刻组件会自动调用执行的函数。React生命周期函数如下:
Initialzation:初始化
在constructor里初始化数据 state 和 props
Mounting:挂载
只在组件第一次被挂载页面时执行
- componentWillMount 在组件即将被(还没有)挂载到页面的时刻自动执行
- render 挂载渲染页面
- componentDidMount 在组件被挂载到页面之后自动执行
Updation:组件更新
state或者props发生变化
-
componentWillReceiveProps (只在props发生改变时执行)
一个组件要从父组件接受参数props,
然后只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被执行
如果这个组件第一次存在于父组件中,不会执行。如果这个组件之前已经存在于父组件中,才会执行
-
shouldComponentUpdate 组件被更新之前自动执行,要求返回一个布尔值,组件要更新吗?
-
componentWillUpdate 组件被更新之前自动执行,而且是在shouldComponentUpdate 之后执行。如果shouldComponentUpdate返回true,它才执行,返回false,它不会执行。
-
render
-
componentDidUpdate 组件更新完成之后执行
Unmounting 把组件从页面上卸载
- componentWillUnmount 在组件即将被从页面中卸载时执行
所有的生命周期函数都可以不存在,但是render必须存在,React.Component默认内置了其他所有的生命周期函数,唯独没有内置render生命周期函数,所以组件必须自己来定义render
子组件render执行的场景:
-
state 或者 props发生改变的时候
-
当前组件的父组件render重新执行的时候(性能不好,可以使用生命周期函数优化,解决办法如下:)
生命周期函数使用场景
- 解决父组件render重新执行时,使得子组件render不必要的执行
// 子组件
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.content !== this.props.content) {
return true
} else {
return false
` }
}
- ajax请求
// componentWillMount也是没有问题的,
// 但是服务器端或者react native里更高级的东西时,可能会和其他技术产生冲突
// constructor也可以,但是建议还是放在componentDidMount
componentDidMount() {
// api接口请求
}
性能优化
// 1 绑定作用域放在constructor
constructor() {
this.handleMethods = this.handleMethods.bind(this)
}
// 2 this.setState方法
// 3 虚拟DOM、同层比对、key值的使用
// 4 shouldComponentUpdate的使用避免不必要的render渲染