react组件设计
VDOM和diff算法
- 什么是vdom
真实dom操作会触发重排重绘,比较耗性能,引入vdom的概念。vdom是一个js对象,可以完整描述dom结构。当状态变更时,重新渲染这个js对象。 - react中的vdom
jsx是对js语法的扩展,使用它能够代替常规的js,使用html语法写js对象。webpack+babel编译时,会替换jsx为ReaceDom.createElement(type, props, ...children),这个函数返回一个js对象,能够完整描述dom结构,为vdom
render(){
return (
<div id="div">
<span className="span">hello</span>
</div>
)
}
webpack+babel=>
render(){
return React.createElement('div', {id: 'div'},
React.createElement('span', {className: 'span'}, 'hello')
)
}
- ReactDom.render(vdom,container)可以将vdom转换为真实的dom并追加到container中,通过遍历递归vdom树,根据type不同,执行不同的逻辑,type为1生成原生元素,如div,为2为类组件,需要将类组件实例化并执行其render将返回的dom初始化,为3是函数组件,需要执行函数组件,并将返回的dom初始化
- 实现react思路
三个核心api:React.createElement()和ReactDOM.render()
1、React.createElement()创建出虚拟dom
// react.js
createElement(type, props, ...children){
props.children = children
let vtype = typeof type === 'string'?1:2 //1: div 2:Component
return {vtype, type, props} //vdom
}
2、ReactDOM.render():将vdom渲染成真实dom
//reactDom.js
function render(vdom, container){
const dom = initDom(vdom)
container.appendChild(dom)
}
function initDom(vnode){
// 根据vtype不同,创建不同的node
const {vtype} = vnode
if(!vtype){ // 文本节点
return document.createTextNode()
}
if(vtype === 1){ //元素节点
return createElementNode(vnode)
}
if(vtype === 2){ // Component
return createClassComp(vNode)
}
}
function createElementNode({vtype, type, props}){
const node = document.createElement(type) //创建一个元素节点
// 处理属性
const {children, ...rest} = props
Object.keys(rest).forEach(k=>{
// className,htmlFor,on,style, ...特殊的属性特殊处理
node.setAttribute(k, rest[k])
})
// 递归处理chidren
children.forEach(c=>{
node.appendChild(initDom)
})
return node
}
function createClassComp({vtype, type, props}){
}
- diff算法
1、diff算法解决了什么问题?
dom操作很慢,轻微的操作都会导致页面重排重绘。相对于dom对象,js对象处理起来更快更简单。通过diff算法对比新旧js对象之间的差异,可以批量最小化执行dom操作,从而提高性能
2、react中在哪里使用了diff算法?
react中用jsx语法描述视图,通过babel-loader转义后变为React.createElement(type, props,...children)的形式,该函数生成vdom用来描述dom结构。如果状态变化,如this.setState(),vdom做出相应变化,在通过diff算法对比新旧vdom区别,做出最终的操作
3、diff算法如何进行优化的?
我对diff算法,没有具体研究过它的源码,知道他的一些策略。进行diff算法之前,dom操作复杂度是O(n^3)优化后为O(n)
策略:
a、同级比较,不同级没有必要比较了
b、拥有相同类的组件会生成相似的树形结构,不同类类的组件生成不同的树形结构。如果两个组件类型都不同,它的chidren也不需要比了,直接删除生成一个新的。通过这种方式决定使用删除、新增还是更新操作。
c、对于同一层级的子节点,通过唯一的key进行区分
实现redux
- redux的主要工作流程
dispatch->action->reducer->return new state
store.js
import {createStore, applyMiddware} from 'redux'
//修改store数据
const reducer = (state={count: 0}, action)=>{
switch(action.type){
case 'add':
state['count']++
return state
case 'minus':
state['count']--
return state
default:
return state
}
}
const store = createStore(reducer, applyMiddware())
// index.js
import store from './store'
store.subscribe(()=>{
render ...
})
// action dispatch
const action = {type:'add'}
store.dispatch(action)
核心api:createStore=>store,store.getState,store.subscribe,store.dispatch
redux.js //观察者模式
let listens = []
export function createStore(reducer, enhancer){
let currentState;
function getState(){
return currentState
}
function subscribe(fn){
listens.push(fn)
}
function dispatch(action){
currentState = reducer(currentState, action)
listens.forEach(v=>{v()})
return action
}
return {getState, subscribe, dispatch}
}
实现react-redux
- 工作流
import {connect, Provider} from 'react-redux'
// 核心 Provider 利用react的Context组件值共享原理,
<Provider store={store}><App /></Provider>
const mapStateToProps = state => ({count: state.count}) // actionCreator
const mapDispatchToProps = {add}
// connect(mapStateToProps,mapDispatchToProps)(Component) //高阶组件
- 核心代码
//react-redux.js
export Class Provider extends React.Component{
// Context值共享api
static childrenContextType = {
store: PropTypes.object
}
getChildContext(){
return {store: this.store}
}
constructor(props, context){
super(props, context)
this.store = props.store
}
render(){
{this.props.children}
}
}
export connect(mapStateToProps, mapDispatchToProps){
return Component => {
return class ConnectComponent extends React.Component{
static contextType = {
store: PropsTypes.object
}
constructor(props, context){
this.state = {
props: {}
}
}
ComponentDidUpdate(){
const {store} = this.context
store.subscribe(()=>this.update())
update()
}
update(){
const {store} = this.context
const mapState = mapDispatchToProps(store.getState())
const mapDispatch = this.bindActionCreators(mapDispatchToProps, store.dispatch)
this.setState({
props: {...mapState,...mapDispatch}
})
}
bindCreators(creators, dispatch){
let ret = {}
Object.keys(creators).forEach(key=>{
ret[key] = ()=>dispatch(creators[key]())
})
return ret
}
render(){
return <Component ...this.state.props></Component>
}
}
}
}