对react技术栈的一些理解

前端之家收集整理的这篇文章主要介绍了对react技术栈的一些理解前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

目的

本篇文章主要帮助大家了解下react技术栈相关的概念,以及为什么我们需要引入这些,他们能解决什么问题。

React

为什么选择react,而不是vue2

vue2的优点

vue1没有加入虚拟DOM,做服务端渲染很难,所以vue2引入了虚拟DOM的机制,而且由于vue2的响应式原理,所以天然的就比react性能好,react的更新是通过顶层组件的state变化触发整个组件的重新渲染,而vue2由于其是通过getter/setter来进行数据管理,所以可以准确的定位到需要重新渲染的节点,避免了无效的re-render

vue2的缺点

native支持不好,weex很厉害但是目前只有阿里用于生产环境,而react native有着大量的成熟案例,如手机QQ

为什么没有选择riot

riot的优点

小,用在移动端很合适

riot的缺点

小的缺点很多,但是比较好克服,最大的缺点还是在于native端,如果想用riot实现native端的话,需要造轮子,写就是自己写一套Native Bridge,来进行jsObjective C通信,难度太大,需要引入js引擎等高大上的东西(Native Bridge基本上可以理解为一个浏览器内核,而且肯定是C++写)。

native的基本原理

一个线程为js引擎,执行打包好的js,主线程负责UI绘制,js需要绘制UI时会向主线程发出一个命令,主线程接收到命令后执行相应的绘制逻辑,Objective C执行的结果会经过层层回调通过js引擎传回给js

redux

react + redux的缺点

利用redux做数据管理的话,reduxstore会被放置到最顶层组件的state中,也就是react-redux为我们提供的Provider组件。这样就是意味着每次store发生变化就会重新渲染整个应用,也就是触发所有组件的render方法,每次都会触发diff,但是这种大多是无意义的,这也是产生性能瓶颈的地方:

如下面代码

  1. render() {
  2. const { child1Data,child2Data } = this.props.store;
  3. return (
  4. <div> <Child1 data="child1Data" /> <Child2 data="child2Data" /> </div> ); }

假如只有child1Data发生变化,而child2Data并没有发生变化,理论上来说我们只想触发Child1render,但事实上我们同时会触发Child2render,这次显然是无意义的,所以需要来解决这个问题。

引入purerender

react的生命周期函数中有一个shouldComponentUpdate,根据其返回的值来决定是否需要来触发组件的rendershouldComponentUpdate默认返回的是true,也就是无论什么情况都会触发renderpurerender改善的就是这个生命周期,根据传入的stateprops来进行简单的判断,从而决定是否需要进行render,为什么说只是进行了简单的判断,来看其判断部分代码
(注:利用connect将组件与redux关联起来的容器不需要加purerender ,因为这个工作react-redux已经替我们做好了

  1. // is可以理解为Object.is()
  2. function shallowEqual(objA,objB) {
  3. if (is(objA,objB)) {
  4. return true;
  5. }
  6. // 非引用类型,且不相等直接返回
  7. if (typeof objA !== 'object' || objA === null ||
  8. typeof objB !== 'object' || objB === null) {
  9. return false;
  10. }
  11. const keysA = Object.keys(objA),keysB = Object.keys(objB);
  12.  
  13. if (keysA.length !== keysB.length) {
  14. return false;
  15. }
  16. // 问题所在,仅仅是比较了第一层,假设引用没变,不会触发更新
  17. for (let i = 0; i < keysA.length; i++) {
  18. if (
  19. !hasOwnProperty.call(objB,keysA[i]) ||
  20. !is(objA[keysA[i]],objB[keysB][i])
  21. ) {
  22. return false;
  23. }
  24. }
  25. return true;
  26. }

注意:使用react时一定注意不要在render函数中进行函数bind,因为这样每次props中会有属性的引用改变,一定会触发更新

对于一般的情况来说purerender已经足够,可以减少一些re-render,但是不是很彻底,比如:
- 情况一:

  1. // 之前的数据:
  2. let person = {
  3. name: 'zp1996',age: 21
  4. };
  5. // 改变引用
  6. person = {
  7. name: 'zp1996',age: 21
  8. };

(注:上面场景我做了下简化,真实中可能是从服务器端获得的数据,比如帖子列表这种)
明显的引用发生了改变,所以会触发re-render,但是明显的是我的数据完全没有变化,根本不用进行diff
- 情况二:

  1. const data = {
  2. person: {
  3. students: [{
  4. name: 'zp1996',age: 21
  5. }]
  6. }
  7. };
  8. // 加入一个新的学生,数据结构会变成这样
  9. {
  10. person: {
  11. students: [{
  12. name: 'zp1996',age: 20
  13. },{
  14. name: 'zpy',age: 21
  15. }]
  16. }
  17. }

假如是这种情况,引用根本没有发生变化,所以就不会触发re-render,每次改变一个小的地方,就需要将整个的数据重新生成一个,这样造成了内存的不必要的浪费。

利用immutable解决

很容易想到的是在shouldComponentUpdate中进行深度比较,用递归的方式来进行比较,这样的代价同样很大,并不是一个有效的解决方案。为了解决这个问题,需要引入另一个库——immutable,其思想是强调不可变数据,一个Immutable Data的创建就是一个不可变的,需要变化时不是利用深拷贝,而是仅仅改变这个变化的节点和其父节点,其余节点仍是共享内存。同样的这样的一个强大的框架也是非常大,压缩过后仍然有50k

还有问题吗?

我们希望的是reducer中保持简单,从服务端请求回来的数据直接存在store中,看个例子:

  1. // 从服务端拉回来的数据
  2. {
  3. students: {
  4. 'id_111': {
  5. name: 'zp1996',age: 21
  6. }
  7. }
  8. }
  9. // 最终组件希望我们传入这样的数据
  10. {
  11. students: [{
  12. name: 'zp1996',age: 21,id: 'id_111'
  13. }]
  14. }
  15. // 一般会在connect中对数据进行整理
  16. const mapStateToProp = state => {
  17. const { students } = state,res = [];
  18. for (let key in students) {
  19. if (students.hasOwnProperty(key)) {
  20. let obj = students[key];
  21. obj[id] = key;
  22. res.push(obj);
  23. }
  24. }
  25. return res;
  26. };
  27. @connect(
  28. mapStateToProp,// 被叫做selector
  29. );

每次store变化后,也就是执行一次dispatch之后都会执行利用subscribe方法注册的回调(注册的回调就是connect的第一个参数,也就是selector),这样就意味着,尽管students并没有发生变化还是会触发一次数据结构的重整,这种显然是一种浪费,所以这个过程也需要优化:

redux强调的是函数式编程,对于函数式编程来说有一个很明显的特点就是易于缓存,对于一个函数而言,给定相同的输入肯定会得到相同的输出,而selector也全部为纯函数,同时connectmapStateToProp参数也支持返回一个函数reselect库就是这个思想,先来看看基本用法

  1. // createSelector的最后一个参数作为计算函数
  2. const state = { num: 10 },selector = state => state.num,reSelector = createSelector(
  3. selector,a => {
  4. console.log('被调用了')
  5. return a * a;
  6. }
  7. );
  8.  
  9. console.log(reSelector(state)); // 第一次计算
  10. console.log(reSelector(state)); // 拿缓存
  11. console.log(reSelector(state)); // 拿缓存
  12. state.num = 100;
  13. console.log(reSelector(state)); // 值发生改变,计算
  14. console.log(reSelector(state)); // 拿缓存
  15. console.log(reSelector(state)); // 拿缓存

实现也是非常简单,就是对传入的参数进行判断,如果与之前一样则直接返回结果

  1. function defaultMemoize(func,equalityCheck = defaultEqualityCheck) {
  2. let lastArgs = null,lastResult = null;
  3. const isEqualToLastArg = (value,index) => equalityCheck(value,lastArgs[index]);
  4. return (...args) => {
  5. // 检测输入是否相等,不等或者第一次执行的话执行函数,反之拿之间的结果
  6. if (
  7. lastArgs === null ||
  8. lastArgs.length !== args.length ||
  9. !args.every(isEqualToLastArg)
  10. ) {
  11. lastResult = func(...args);
  12. }
  13. lastArgs = args;
  14. return lastResult;
  15. };
  16. }

redux太复杂?尝试mobx

redux会引入很多的概念,同时代码量也会很多,而mobx要更为简单。mobx给我的感觉就像是把vue的响应式数据那一套给拿了出来,给了我们极大的自由度,可以利用OOP那一套来建立模型,而用redux必须利用redux的那些套路写代码;同时在性能上也会有一些提升。但是同时也会带来很大的问题:state满天飞是避免不了的,但是提供了一个strict模式,要求数据必须利用action来进行更改,但是我用了之后发现并没有什么作用,而且组件外是可以随意更改数据的。 还有一个小问题就是热更新,根本没有找到热更新的解决方案,每次还得手动刷新页面

最后

redux还有一块很重要的部分,那就是异步处理,目前本人只用过redux-thunk,所以关于这个方面没(水)有(平)讲(不)到(够),同时哪里有错误也请大家指出。

猜你在找的React相关文章