Virtual Dom
在浏览器环境下,DOM操作远比JS操作开销大,且项目越复杂,DOM操作的开销越大,为了提升DOM渲染的效率,需要遵循的原则是尽量减少DOM操作。于是Virtual Dom横空出世!
Virtual Dom顾名思义,是使用JS模拟DOM结构;并把DOM变化的对比,放在JS层来做,以此来提高重绘性能(将开销大的Dom操作转化为高效的JS操作)
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
</ul>
//用JS模拟DOM如下:
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
},
{
tag: 'li',
attrs: {className: 'item'},
children: ['Item 1']
}
]
}
snabbdom
snabbdom为virtual dom的一个实现库,其核心API有2:
h()
:创建virtual dom
patch(vnode, mewnode)
:渲染前对比两个虚拟节点后并完成真实渲染
diff
virtual dom中(React、Vue)的diff算法起源于Linux的diff
命令,用于比对文本的不同之处,是一个古老的命令。在virtual dom操作中,找出需要重新渲染的节点的过程,就是React中的diff算法。
//简略模拟diff:
function creatElement(vnode){
var tag = vnode.tag
var attrs = vnode.attrs || {}
var children = vnode.children || {}
if(!tag){
return null
}
var elem = document.creatElement(tag)
var attrName
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)){
elem.setAttribute(attrName, attrs[attrName])
}
}
chlidren.forEach((childVnode) => {
elem.appendChild(creatElement(childVnode))//递归
})
return elem
}
function updateChildren(Vnode, newVnode){
var children = vnode.children || {}
var newChildren = newVnode.children || {}
children.forEach((childVnode, index) => {
var newChildVnode = newChildren[index]
if(childVnode.tag === newChildVnode.tag){
updateChildren(childVnode, newChildVnode)
} else {
replace(childVnode, newChildVnode)
}
})
}
JSX
在React中负责VIew的编写,其本质上是语法糖,由于浏览器无法识别JSX,实际运行时由React库中的核心函数React.creatElement
将JSX翻译成JS,再由JS渲染为HTML,类似于virtual dom经由h()
转为真实节点一样。
React首次渲染时,调用patch(container, vnode)
通过setState()
函数再次渲染时,调用patch(newVnode, vnode)
JSX自定义组件的解析过程
-
React.creatElement(App, null)
首先调用React的核心函数,传入自定义组件App
-
var app = new App()
App
为class
,进行类的实例化 -
return app.render()
调用类实例的render()
方法,并继续步骤1,直至没有自定义组件为止
setState()方法
- 异步
setState()方法是异步的,这是因为一次操作中,可能执行多次setState
,根据浏览器单线程的特性和性能上的考量,无需每次执行setState
的时候都重新渲染,只需进行最后一次渲染即可。
......
{
setState({a: 1})
setState({a: 2})
setState({a: 3})//只执行最后一次,前两次不执行
}
......
- 过程
1 每个React组件实例,均有继承自Component
类的renderComponent
方法。
2 执行renderComponent
方法会再次执行组件的render
方法。
3render
方法返回newVnode,同时系统缓存有preVnode的信息。
4 执行patch(preVnode, newVnode)
。
React生命周期相关
组件生命周期shouldComponentUpdate
React开发时,一个很奇妙的事情就是当state
或props
未发生改变时,组件依然会重新渲染,所以当追求性能的时候,shouldComponentUpdate
就派上了用场。shouldComponentUpdate
生命周期函数是重渲染时render()
函数调用前被调用的函数,它接受两个参数:nextProps
和nextState
,分别表示下一个props
和下一个state
。并且,当函数返回false
时候,阻止接下来的render()
的调用,阻止组件重渲染,而返回true
时,组件照常重渲染。
//通过对比属性来确定组件是否更新
shouldComponentUpdate(nextProps,nextState){
if(nextProps.numberObject.number == this.props.numberObject.number){
return false
}
return true
}
一个警告
Warning: setState(...): Can only update a mounted or mounting
component. This usually means you called setState() on an
unmounted component. This is a no-op. Please check the code for
the OrderTableList component.
在组件生命周期中,设立标志位来检验组件是否挂载,可规避此警告
componentDidMount(){
this.mounted = true
//some code
}
if(this.mounted){
this.setState({})
}
componentWillUnmount(){
this.mounted = false
}
react数据流
在并不复杂的webapp中,其实是可以不用redux的!
- 父组件向子组件传递数据,类似如下的思路
<Father>
<Son data={this.state.data}/>
</Father>
- 子组件向父组件传递数据
<Father>
<Son changeData={this.addData.bind(this)}/>
</Father>
//Father中有addData()操作数据,并将此方法向下传递下去(到Son)
addData(){
//do sth
}
//Son中有方法负责传递数据,调用this.props上传递下来的方法处理,并将自身数据传入其中
do(){
this.props.changeData(mydata)
}
- 子组件与子组件间传递数据
这里说的是有公共父组件的应用场景,即将子组件一的数据上传到父组件,经由父组件中转后将数据下发到子组件二 - ref
当父组件需要直接操作子组件时,即调用子组件方法,可以使用ref
<Father>
<Son ref='loginAlert'/>
</Father>
~~~~~~~~~
~~~~~~~~~
this.refs.loginAlert.someFuc(this为Father组件上下文)
关于父组件更新而子组件不更新的问题
react中,当父组件通过props向子组件传递数据时,当父组件更新时,子组件会重新render,注意是重新render,子组件并不会完成卸载——加载过程,因此,constructor构造函数并不会再次执行,通过this.state
语句自然不会传入props的值。这种情况下,子组件可以定义为“无状态”组件,直接使用this.props.data即可。
Immutable.js
var a = 'a'
var b = a
var b = 'b'
console.log(a) //b
修改引用型对象是一样不安全的行为
var a = 'a'
var b = Object.assign({}, a)
console.log(a === b) //false
可以使用ES6方法生成指向不同内存地址的新对象(concat(),...均可),可以使操作对象的行为变得相对安全,但是这种不断新开辟内存的行为会增大系统的开销,此时immutable.js隆重登场 ,其核心思想为,对象一经创建,其不会被改变;无论对其进行什么操作,都返回一个新对象;老对象始终不变,而新对象通过树形结构共享原则,既保留原来对象不变,又保留变化的这个部分,而因为每次变化仅为一部分,避免了深拷贝带来的性能损耗,同时可以进行任意时刻的数据回溯。
Redux
Redux数据流
Redux数据流-
store
是Redux
的核心,其集成了reducers
和一些中间件,Action
发出动作后,集成了reducers
的store
随即发生状态变化,然后数据便会从根容器(Provider
)向下传递;由于数据在全局有且只有一份(Store
),因此在React
应用中频繁修改state
容易引起数据混乱,因此在Redux
数据流中,需要使用Immutable Data
。 -
Redux
数据流中,一个Action
有一个或者多个Reducer
与之对应,多个Reducer
用combineReducer
包裹并传入Store
中 -
容器组件和视图组件
容器和视图组件
中间件
const logger = (store) => (next) => (action) => {
//do something
} //科里化函数
构建store
import {creatStore, applyMiddleware} from 'redux'
import {fromJS} from 'immutable'
import thunk from 'redux-thunk'
import creatLogger from 'redux-logger'
import promiseMiddleware from 'redux-promise'
import reducers from './reducers'
const middlewares = [thunk , promiseMiddleware, creatLogger()]
function configerStore( initialState = fromJS({}){
const store = creatStore(reducers(), initialState, applyMiddleware(...middlewares))
if(module.hot){
module.hot.accept( () => {
store.replaceReducer(require('./reducers').default) //热重载更新reducers
})
}
return store
}
export default configerStore
connect
用于建立store和actions的连接,使reducers可以接收到发出的actions,从而改变store中的数据,即将action和reducer都绑定在一个实例上。connect()接受4个参数:
mapStateToProps #把store中的数据作为属性绑定到组件上,从而可以...this.props获取
mapDispatchToProps #方法的绑定,从而可以this.props获取action调用
mergeProps
options
Mobx
Mobx数据流相较Redux而言,概念更少。Mobx基于一份数据进行修改(Redux数据基于浅拷贝,每次更新都生成新的数据对象,但是由于浅拷贝和虚拟DOM的存在,对于整体性能的影响有限),因其对store数据的修改可以使用JS修改普通对象的方式,过程比较灵活(不严谨)。
const mobxStore = observable(
count: 0,
add: action(function(num){
this.count += num
})
)
mobxStore.add(1)
- Mobx应用中用Class组织
- Mobx重要概念
@observable
@action
@computed
autorun()//数据store更新时调用
- 通过@inject('storeName')向组件注入数据
- 通过@observer监听组件
React16新特性
- rollup打包,体积小
- 使用Fiber重写
- 一些实用的功能:
- error boundary,捕获React渲染过程中出现的错误,通过新的钩子函数:
componentIDidCatch
- New render return types,可以在Render方法中返回Array和String,也可以使用
Fragment
代替没有实际意义的div
- Portals,用于创建浮层或者Popup组件
- Better sever-side rendering
React中使用PropTypes进行类型检查
从React15.5起,React内置的类型检测库开始作为独立的存在,开发者可以通过‘prop-types’类型检测库捕获更多的bug,PropTypes 输出了一系列的验证器,可以用来确保接收到的参数是有效的。当给 props 传递了一个不正确的值时,JavaScript控制台将会显示一条警告。
import PropTypes from 'prop-types'
class A extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
A.PropTypes = {
name: PropTypes.string
}
同一组件无法重新渲染的解决方案
当使用路由切换时,不同的路由下对应的同一个组件 ,这时候React并不会重新渲染组件,此时只是传入的props有了一定的变化,这时需要利用React的钩子方法:componentWillReceiveProps
,通过比对props,来完成新数据的获取,进而重新渲染组件。
网友评论