作为react的粉丝,当然要吐槽一下react组件通信问题。react的单向数据流是组件通信的一大阻碍,只允许父组件向子组件传值,子组件向父组件传值只能通过父组件向子组件传递回调函数实现。如果在深层次的组件结构当中,复杂与繁多的回调会大大增加程序的维护难度与可读性,延长开发周期。即使react实现的再优秀,组件通信没处理好,还是一坨屎。redux给react的组件通信带来解决方案:构建一个大的状态作用域(一个大组件),大组件里的所有组件状态变化都只需要维护大组件,更新的状态都有大组件开始下发,这样就避免了子组件向父组件传值需要回调方法的问题。
redux优点:贯彻了react的单向数据流思想,避免父子组件通信时繁琐的回调,清晰的数据流简化了业务逻辑实现,提高应用维护效率。
redux槽点:为了数据中心化与自动化测试分离出了action与redcuer与mapStateToProps,简单的数据操作被复杂化了。每次dispatch都会重新渲染状态作用域里的所有组件,虽然react使用diff算法筛选出需要改变的节点进行渲染(优化了性能),但是在庞大以及深层次的组件结构中,diff带来的消耗还是不可忽视的。
我格外厌恶redux繁琐的操作,所以自己实现了一个简化版的redux。把action与redcuer合并、废弃了mapStateToProps简化了redux的数据操作。由于action与redcuer的合并后存在数据依赖,所以合并后的action不能做数据自动化测试。除了简化redux的繁琐的数据操作之外,还通过监听组件里的动态数据与dispatch更新的数据对比检测该组件是否需要重新渲染,解决redux最大的弊端【redux组件渲染弊端:redux dispatch后没有变化的组件也会被重新渲染,这是不必要的性能消耗】。我还丰富了Component基类给每个组件提供了重新渲染检测功能。
redux-simple.s实现如下:
1 import React,{Component,createElement} from "react"; 2 var event=function(){ 3 var propsment={}; 4 var dispatchProps={}; 5 return { 6 //添加监听属性 7 listenProps:function(props,_this){ 8 9 if(_this.constructor&&_this.constructor.name){ 10 propsment[_this.constructor.name]=props; 11 }else{ 12 throw new Error("监听对象不是react组件"); 13 } 14 }, 15 //清空监听的属性 16 emptyProps:function(){ 17 propsment={}; 18 }, 19 //检测是否需要更新 20 checkProps:function(_this){ 21 var data=propsment[_this._reactInternalInstance.getName()]||[]; 22 var propKeys=Object.keys(dispatchProps); 23 //没有监听属性的组件直接更新 24 if(!data.length){ 25 return true; 26 } 27 for(var i=0;i<data.length;i++){ 28 //判断监听的属性是否存在于更新数据里面,存在则更新组件 29 if(propKeys.indexOf(data[i])!=-1){ 30 return true; 31 } 32 } 33 return false; 34 }, 35 //监听更新的数据 36 listenDispatch:function(props){ 37 dispatchProps=props; 38 } 39 } 40 41 42 }() 43 export function connect(App,actions){ 44 let dispatch,getState; 45 dispatch=getState=function(){ 46 throw new Error("组件还没初始化"); 47 } 48 class Main extends Component{ 49 constructor(props){ 50 super(props); 51 this.constructor.setState=dispatch=(data)=>{ 52 //监听更新的数据 53 event.listenDispatch(data); 54 //更新store 55 this.setState(Object.assign({},this.state,data)); 56 } 57 getState=()=>{ 58 return Object.assign({},this.state); 59 } 60 this.actions=bindAction(actions,dispatch,getState||{}); 61 } 62 render(){ 63 return <div> 64 {createElement(App,Object.assign({dispatch:dispatch,listenProps:event.listenProps,getState:getState,actions:this.actions},this.props,this.state))} 65 </div> 66 } 67 } 68 return Main; 69 } 70 //action方法绑定dispatch并支持异步 71 function bindActionCreators(action,dispatch,getState){ 72 if(typeof action==="function"){ 73 return function(){ 74 [].unshift.call(arguments,getState()); 75 var newState=action.apply(null,arguments); 76 if(typeof newState==="function"){ 77 newState(dispatch) 78 }else if(isObject(newState)){ 79 dispatch(newState); 80 }else{ 81 throw new Error("action方法返回值只能是函数或者对象"); 82 } 83 } 84 } 85 } 86 //给所有的action方法绑定dispatch 87 export function bindAction(actions,dispatch,getState){ 88 var newActions={}; 89 if(isObject(actions)){ 90 var keys=Object.keys(actions); 91 keys.forEach(function(value){ 92 newActions[value]=bindActionCreators(actions[value],dispatch,getState) 93 }) 94 return newActions; 95 }else if(actions){ 96 throw new Error("actions必须是对象") 97 } 98 } 99 //检测是否是一个对象 100 let isObject=(data)=>{ 101 if(Object.prototype.toString.call(data)=="[object Object]"){ 102 return true; 103 }else{ 104 return false; 105 } 106 } 107 //构建新的组件基类 108 export default class NewCom extends Component{ 109 shouldComponentUpdate(nextProps){ 110 return event.checkProps(this,nextProps); //检测是否重新渲染该组件 111 } 112 componentWillUnmount(){ 113 event.emptyrProps(); //清空监听列表 114 } 115 }
使用实例如下:
1 import React from "react"; 2 import ReactDOM from "react-dom"; 3 import Component,{connect} from "./react-simple.js"; 4 class Test01 extends Component{ 5 constructor(props){ 6 super(props); 7 this.props.listenProps(["test01"],this) 8 } 9 render() { 10 console.log(this.props.test01); 11 return <h1>{this.props.test01}</h1>; 12 } 13 } 14 class Test02 extends Component{ 15 constructor(props){ 16 super(props); 17 this.props.listenProps(["test02"],this) 18 } 19 clickHandler(){ 20 this.props.dispatch({ 21 test01:789, 22 }) 23 } 24 render() { 25 console.log(this.props.test02); 26 return <h1 onClick={this.clickHandler.bind(this)}>{this.props.test02}</h1>; 27 } 28 } 29 class Main extends Component{ 30 componentDidMount(){ 31 this.props.dispatch({ 32 test01:123, 33 test02:456, 34 }) 35 } 36 render() { 37 return <div> 38 <Test01 {...this.props}/> 39 <Test02 {...this.props} /> 40 </div>; 41 } 42 } 43 var NewCom=connect(Main); 44 ReactDOM.render( 45 <NewCom />, 46 document.getElementById("container") 47 )