13

react 部分

为什么不能用数组下标来作为react组件中的key?

react 使用diff算法,使用key来做同级比对。如果使用数组下标作为key,有以下情况:
在数组头部或中部插入或删除元素: 所有key对应的节点的值发生更改,进行重新渲染。造成性能损耗
而如果使用数组中唯一值来作为key:不管是在何处插入或删除节点,其他key对应的节点的值未发生更改,只需插入或删除操作的数组节点。

react shouldComponentUpdate 函数的作用?

使用shouldComponentUpdate()以让React知道当前状态或属性的改变是否不影响组件的输出,默认返回ture,返回false时不会重写render,而且该方法并不会在初始化渲染或当使用forceUpdate()时被调用.

如果在组件树的根节点发生更新则所有子节点都会发生更新,这时对所有子节点使用shouldComponentUpdate来减少子节点的渲染,无疑会增加很多代码。我们可以选择使用PureComponent来处理。

React.PureComponent 和 React.Component

PureComponent 和 Component的区别是:Component需要手动实现 shouldComponentUpdate,而PureComponent通过浅对比默认实现了shouldComponentUpdate方法

浅比较(shallowEqual),即react源码中的一个函数,然后根据下面的方法进行是不是PureComponent的判断,帮我们做了本来应该我们在shouldComponentUpdate中做的事情

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}

注意: 浅比较只比较了第一层,复杂数据结构可能会导致更新问题

总结: PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最佳情况是展示组件

React.memo

React.memo为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。
如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

React.memo 依然是浅比较(默认)。在React.memo可以自定义其比较方法的实现。

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

和PureComponent的区别:

  • memo为函数组件,PureComponent为类组件
  • memo 默认和PureComponent为浅比较
  • memo 如果相同props的情况下将跳过渲染直接服用最近一次渲染的效果

配合Immutable来实现比较

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

因为使用Immutable 任何修改或添加删除操作都会返回新的Immutable 对象,所以只需简单比较即可,可参考如下代码

// 使用 immutable.js 后
let map1 = Immutable.Map({a:1, b:1, c:1});
let map2 = Immutable.Map({a:1, b:1, c:1});
map1 === map2;             // false 
// 为了直接比较对象的值,immutable.js 提供了 \`Immutable.is\` 来做『值比较』
Immutable.is(map1, map2);  // true

流行的 Immutable 库有两个:

  • immutable.js
  • seamless-immutable
// 原来的写法
let foo = {a: {b: 1}};
let bar = foo;
bar.a.b = 2;
console.log(foo.a.b);  // 打印 2
console.log(foo === bar);  //  打印 true

// 使用 immutable.js 后
import Immutable from 'immutable';
foo = Immutable.fromJS({a: {b: 1}});
bar = foo.setIn(['a', 'b'], 2);   // 使用 setIn 赋值
console.log(foo.getIn(['a', 'b']));  // 使用 getIn 取值,打印 1
console.log(foo === bar);  //  打印 false

// 使用  seamless-immutable.js 后
import SImmutable from 'seamless-immutable';
foo = SImmutable({a: {b: 1}})
bar = foo.merge({a: { b: 2}})   // 使用 merge 赋值
console.log(foo.a.b);  // 像原生 Object 一样取值,打印 1

console.log(foo === bar);  //  打印 false

Immutable 优点:
1、Immutable 降低了 Mutable 带来的复杂度
2、节省内存(Immutable.js 使用了 Structure Sharing 会尽量复用内存。没有被引用的对象会被垃圾回收。)

import { Map} from 'immutable';
let a = Map({
  select: 'users',
  filter: Map({ name: 'Cam' })
})
let b = a.set('select', 'people');

a === b; // false

a.get('filter') === b.get('filter'); // true  a 和 b 共享了没有变化的 \`filter\` 节点

3、拥抱函数式编程

Immutable 本身就是函数式编程中的概念,纯函数式编程比面向对象更适用于前端开发。因为只要输入一致,输出必然一致,这样开发的组件更易于调试和组装。

4、Undo/Redo,Copy/Paste,甚至时间旅行这些功能做起来小菜一碟

因为每次数据都是不一样的,只要把这些数据放到一个数组里储存起来,想回退到哪里就拿出对应数据即可,很容易开发出撤销重做这种功能。

5、并发安全

传统的并发非常难做,因为要处理各种数据不一致问题,因此『聪明人』发明了各种锁来解决。但使用了 Immutable 之后,数据天生是不可变的,并发锁就不需要了

Immutable 缺点:
1、需要学习新的 API
2、增加了资源文件大小
3、容易与原生对象混淆

参考

react 高阶组件?

高阶组件是参数为组件,返回值为新组件的函数。HOC 是纯函数,没有副作用。HOC 在 React 的第三方库中很常见,例如 Redux 的 connect
高阶组件的作用:

  • 代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
  • 渲染劫持
  • State 抽象和更改
  • Props 更改

高阶组件的实现:

  • 属性代理
  • 反向继承

react 性能优化?

代码层面:

  • 使用return null而不是CSS的display:none来控制节点的显示隐藏。保证同一时间页面的DOM节点尽可能的少。
  • propsstate的数据尽可能简单明了,扁平化。
  • 不要使用数组下标作为key
  • 利用 shouldComponentUpdate 和 PureComponent 避免过多 render function
  • render里面尽量减少新建变量和bind函数,传递参数是尽量减少传递参数的数量。
  • 尽量将 props 和 state 扁平化,只传递 component 需要的 props(传得太多,或者层次传得太深,都会加重shouldComponentUpdate里面的数据比较负担),慎将 component 当作 props 传入

代码体积优化:

性能检测工具:

  • React.addons.Perf
  • Chrome Performance
  • React DevTools

react/vue 父子组件通信方式

react 父子组件通信方式:
1、 父子组件,父->子直接用Props,子->父用callback回调
2、 非父子组件,用发布订阅模式的Event模块
3、 项目复杂的话用Redux、Mobx等全局状态管理管库
4、 Context Api context 会使组件复用性变差
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法.如果你只是想避免层层传递一些属性组件组合(component composition)有时候是一个比 context 更好的解决方案。
组件组合缺点:会使高层组件变得复杂

vue 父子组件通信方式:
1、 父->子组件用Props通信.子->父 事件和回调函数
2、 非父子组件用Event Bus通信
3、 如果项目够复杂,可能需要Vuex等全局状态管理库通信
参考: React/Vue 通信

React Contex

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法.如果你只是想避免层层传递一些属性组件组合(component composition)有时候是一个比 context 更好的解决方案。

  • 相比propsstate,React的Context可以实现跨层级的组件通信。
  • Context API的使用基于生产者消费者模式。生产者一方,通过组件静态属性childContextTypes声明,然后通过实例方法getChildContext()创建Context对象。消费者一方,通过组件静态属性contextTypes申请要用到的Context属性,然后通过实例的context访问Context的属性。
  • 使用Context需要多一些思考,不建议在App中使用Context,但如果开发组件过程中可以确保组件的内聚性,可控可维护,不破坏组件树的依赖关系,影响范围小,可以考虑使用Context解决一些问题。
  • 通过Context暴露API或许在一定程度上给解决一些问题带来便利,但个人认为不是一个很好的实践,需要慎重。
  • 旧版本的Context的更新需要依赖setState(),是不可靠的,不过这个问题在新版的API中得以解决。
  • 可以把Context当做组件的作用域来看待,但是需要关注Context的可控性和影响范围,使用之前,先分析是否真的有必要使用,避免过度使用所带来的一些副作用。
  • 可以把Context当做媒介,进行App级或者组件级的数据共享。
  • 设计开发一个组件,如果这个组件需要多个组件关联组合的,使用Context或许可以更加优雅。

[参考](https://juejin.im/post/5a90e0...

react hook?

为什么会出现react hook:

1、在组件之间复用状态逻辑很难: 在大型的工作项目中用react,你会发现你的项目中实际上很多react组件冗长且难以复用。尤其是那些写成class的组件,它们本身包含了状态(state),所以复用这类组件就变得很麻烦。官方推荐渲染属性(Render Props)高阶组件(Higher-Order Components)。但是这两种方案会生成很多DOM层级嵌套

2、复杂组件变得难以理解(我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。)
3、难以理解的class: class创建组件会遇到this指向的问题。

Hook 是一些可以让你在函数组件里“钩入” React state生命周期等特性的函数

hook 的使用规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)

state hook

useState是react自带的一个hook函数,它的作用就是用来声明状态变量。useState这个函数接收的参数是我们的状态初始值(initial state),它返回了一个数组,这个数组的第[0]项是当前当前的状态值,第[1]项是可以改变状态值的方法函数。

注意:

  • react帮我们记住状态值
  • useState是可以多次调用的
  • react是怎么保证多个useState的相互独立的:react是根据useState出现的顺序来定的(所以只能在函数最外层调用以确保useState的调用顺序)

Effect Hook

_Effect Hook_可以让你在函数组件中执行副作用操作。
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的。

不需要清除

通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它(初始化DOM加载后和更新DOM后)。

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return count
}

注意:

  • react首次渲染和之后的每次渲染都会调用一遍传给useEffect的函数。而之前我们要用两个声明周期函数来分别表示首次渲染(componentDidMount),和之后的更新导致的重新渲染(componentDidUpdate)。
  • useEffect中定义的副作用函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而之前的componentDidMount或componentDidUpdate中的代码则是同步执行的。这种安排对大多数副作用说都是合理的,但有的情况除外,比如我们有时候需要先根据DOM计算出某个元素的尺寸再重新渲染,这时候我们希望这次重新渲染是同步发生的,也就是说它会在浏览器真的去绘制这个页面前发生。
需要清除

有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露。(不用hook只能用DidMount订阅和WillUnmount清除)

useEffect可以在组件渲染后实现各种不同的副作用。有些副作用可能需要清除,所以需要返回一个清除函数

清除函数在什么时候执行:effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。

为什么 effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次?
componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {
    // 取消订阅之前的 friend.id
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // 订阅新的 friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

若未添加DidUpdate函数时:当组件已经显示在屏幕上时,friendprop 发生变化时会发生什么?我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。
忘记正确地处理 componentDidUpdate 是 React 应用中常见的 bug 来源

使用useEffect则不会出现 componentDidUpdate 未正确处理时出现的情况

跳过 Effect 进行性能优化

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。

如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React跳过对 effect 的调用,只要传递数组作为useEffect的第二个可选参数即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

对于有清除操作的 effect 同样适用:

react 16版本为什么将willXX生命周期标记为不安全的?

将Virtual DOM树转换成actual DOM树的最少操作的过程 称为 协调(Reconciliaton)。

react 15 :Stack reconciler

旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。
缺点:在页面元素很多,且需要频繁刷新的场景下,React 15 会出现掉帧的现象。
原因:是大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用setState更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。整个过程是一气呵成,不能被打断的。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象

react 16 :Fiber reconciler

Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API。官方的解释是这样的:

window.requestIdleCallback()会在浏览器空闲时期依次调用函数,这就可以 
让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户 
交互这些延迟触发但关键的事件产生影响。函数一般会按先进先调用的顺序执
行,除    非函数在浏览器调用它之前就到了它的超时时间。

React 框架内部的运作可以分为 3 层:

  • Virtual DOM 层,描述页面长什么样。
  • Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
  • Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。

Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示:

const fiber = {
    stateNode,    // 节点实例
    child,        // 子节点
    sibling,      // 兄弟节点
    return,       // 父节点
}

Fiber Reconciler 每执行一段时间,都会将控制权交回给浏览器,可以分段执行:
为了达到这种效果,就需要有一个调度器 (Scheduler) 来进行任务分配。任务的优先级有六种:

  • synchronous,与之前的Stack Reconciler操作一样,同步执行
  • task,在next tick之前执行
  • animation,下一帧之前执行
  • high,在不久的将来立即执行
  • low,稍微延迟执行也没关系
  • offscreen,下一次render时或scroll时才执行

优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。

Fiber Reconciler 在执行过程中,会分为 2 个阶段。

  • 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
  • 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。

生命周期函数也被分为2个阶段了:

// 第1阶段 render/reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate

// 第2阶段 commit
componentDidMount
componentDidUpdate
componentWillUnmount

第1阶段的生命周期函数可能会被多次调用,默认以low优先级 执行,被高优先级任务打断的话,稍后重新执行。

预废弃的三个生命周期函数都发生在虚拟dom的构建期间,也就是render之前。在将来的React 17中,在dom真正render之前,React中的调度机制可能会不定期的去查看有没有更高优先级的任务,如果有,就打断当前的周期执行函数(哪怕已经执行了一半),等高优先级任务完成,再回来重新执行之前被打断的周期函数。这种新机制对现存周期函数的影响就是它们的调用时机变的复杂而不可预测,这也就是为什么”UNSAFE”。

参考:React Fiber 原理介绍
参考:浅谈React Fiber

react setState

setState(updater, callback)

这个方法是用来告诉react组件数据有更新,有可能需要重新渲染。它是异步的,react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能,所以这就给我们埋了一个坑:

那就是在使用setState改变状态之后,立刻通过this.state去拿最新的状态往往是拿不到的。

setState(function)

设想有一个需求,需要在在onClick里累加两次,如下

  onClick = () => {
    this.setState({ index: this.state.index + 1 });
    this.setState({ index: this.state.index + 1 });
  }
复制代码

在react眼中,这个方法最终会变成

Object.assign(
  previousState,
  {index: state.index+ 1},
  {index: state.index+ 1},
  ...
)
复制代码

由于后面的数据会覆盖前面的更改,所以最终只加了一次.所以如果是下一个state依赖前一个state的话,推荐给setState传function

onClick = () => { 
    this.setState((prevState, props) => {
        return {quantity: prevState.quantity + 1}; 
    }); 
    this.setState((prevState, props) => { 
        return {quantity: prevState.quantity + 1}; 
    }); 
}

参考:揭密React setState

vue和 react 的对比

数据流

 react主张函数式编程,所以推崇纯组件,数据不可变,单向数据流,
 vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。

监听数据变化的实现原理不同

  • Vue 通过 get-0ter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染

组件通信的区别

jsx和模板

react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等。
vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,
可以把html、css、js写到一个文件中,html提供了模板引擎来处理。

HoC 和 mixins
在 Vue 中我们组合不同功能的方式是通过 mixin,而在React中我们通过 HoC (高阶组件)。

React 最早也是使用 mixins 的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了 mixinx 转而使用 HoC.
高阶组件本质就是高阶函数,React 的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。
但是Vue就不行了,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。比如我们定义的模板怎么被编译的?比如声明的props怎么接收到的?这些都是vue创建组件实例的时候隐式干的事。由于vue默默帮我们做了这么多事,所以我们自己如果直接把组件的声明包装一下,返回一个高阶组件,那么这个被包装的组件就无法正常工作了。

写法

react: 类的写法
vue: 声明式的写法

性能优化

react: shouldcomponent
vue: vue中的每个组件内部自动实现了`shouldComponentUpdate`的优化,在vue里面由于依赖追踪系统的存在,当任意数据变动的时,Vue的每一个组件都精确地知道自己是否需要重绘,所以并不需要手动优化。用vue渲染这些组件的时候,数据变了,对应的组件基本上去除了手动优化的必要性。而在react中我们需要手动去优化其性能,但是当数据特别多的时候vue中的watcher也会特别多,从而造成页面卡顿,所以一般数据比较多的大型项目会倾向于使用react。

个人理解Vue和React区别

Redux

redux 原理?

为什么需要Redux: React作为一个组件化开发框架,组件之间存在大量通信,有时这些通信跨越多个组件,或者多个组件之间共享一套数据,简单的父子组件间传值不能满足我们的需求,自然而然地,我们需要有一个地方存取和操作这些公共状态。而redux就为我们提供了一种管理公共状态的方案。我们希望公共状态既能够被全局访问到,又是私有的不能被直接修改。

redux的三个API:

  • getState: 返回当前状态
  • dispatch: 有条件地、具名地修改store的数据(修改的规则从dispatch中抽离出来,就成为了Reducer)
  • subscribe: 订阅store更新(观察者模式)

redux三大原则:

  • 单一数据源
  • state是只读的
  • 使用纯函数来修改

参考
Vuex、Flux、Redux、Redux-saga、Dva、MobX
redux 官网

Redux 为什么要求不变性?

不变性可以为您的应用带来更高的性能,并导致更简单的编程和调试,因为永远不会改变的数据比在您的应用中随意更改的数据更容易推理。

  • Redux 和 React-Redux 都采用浅层平等检查。尤其是:

    • Redux的combineReducers实用程序浅显地检查由它调用的reducer引起的引用更改。
    • React-Redux的connect方法生成的组件会浅显地检查对根状态的引用更改,并从mapStateToProps函数返回值以查看被包装的组件是否实际需要重新呈现。这种浅层检查需要不变性才能正常工作。
  • 不变的数据管理最终使数据处理更安全。
  • 时间旅行调试要求减速器是纯粹的函数,无副作用,以便您可以在不同状态之间正确跳转。

参考:Redux FAQ:不可变数据(Immutable Data)

react-redux

为什么需要react-redux: 一个组件如果想从store存取公用状态,需要进行四步操作:import引入store、getState获取状态、dispatch修改状态、subscribe订阅更新,代码相对冗余,我们想要合并一些重复的操作,而react-redux就提供了一种合并操作的方案

react-redux提供Providerconnect两个API,Provider将store放进this.context里,省去了import这一步,connect将getState、dispatch合并进了this.props,并自动订阅更新

  • Provider: 使用React Contex 将store放入 this.context中
  • connect: 使用装饰器模式实现,所谓装饰器模式,简单地说就是对类的一个包装,动态地拓展类的功能。connect以及React中的高阶组件(HoC)都是这一模式的实现。

redux 各种实现库

参考: https://zhuanlan.zhihu.com/p/...

vuex 和 redux 之间的区别?

从实现原理上来说,最大的区别是两点:

  • Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改
  • Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的(如果看Vuex源码会知道,其实他内部直接创建一个Vue实例用来跟踪数据变化)

React-router

React-router的实现原理?

React-router 有两种模式?这两种模式的区别/兼容性?


我们不动
794 声望44 粉丝

知耻而后勇