介绍一下react
- 以前我们没有jquery的时候,我们大概的流程是从后端通过ajax获取到数据然后使用jquery生成dom结果然后更新到页面当中,但是随着业务发展,我们的项目可能会越来越复杂,我们每次请求到数据,或则数据有更改的时候,我们又需要重新组装一次dom结构,然后更新页面,这样我们手动同步dom和数据的成本就越来越高,而且频繁的操作dom,也使我我们页面的性能慢慢的降低。
- 这个时候mvvm出现了,mvvm的双向数据绑定可以让我们在数据修改的同时同步dom的更新,dom的更新也可以直接同步我们数据的更改,这个特定可以大大降低我们手动去维护dom更新的成本,mvvm为react的特性之一,虽然react属于单项数据流,需要我们手动实现双向数据绑定。
- 有了mvvm还不够,因为如果每次有数据做了更改,然后我们都全量更新dom结构的话,也没办法解决我们频繁操作dom结构(降低了页面性能)的问题,为了解决这个问题,react内部实现了一套虚拟dom结构,也就是用js实现的一套dom结构,他的作用是讲真实dom在js中做一套缓存,每次有数据更改的时候,react内部先使用算法,也就是鼎鼎有名的diff算法对dom结构进行对比,找到那些我们需要新增、更新、删除的dom节点,然后一次性对真实DOM进行更新,这样就大大降低了操作dom的次数。 那么diff算法是怎么运作的呢,首先,diff针对类型不同的节点,会直接判定原来节点需要卸载并且用新的节点来装载卸载的节点的位置;针对于节点类型相同的节点,会对比这个节点的所有属性,如果节点的所有属性相同,那么判定这个节点不需要更新,如果节点属性不相同,那么会判定这个节点需要更新,react会更新并重渲染这个节点。
- react设计之初是主要负责UI层的渲染,虽然每个组件有自己的state,state表示组件的状态,当状态需要变化的时候,需要使用setState更新我们的组件,但是,我们想通过一个组件重渲染它的兄弟组件,我们就需要将组件的状态提升到父组件当中,让父组件的状态来控制这两个组件的重渲染,当我们组件的层次越来越深的时候,状态需要一直往下传,无疑加大了我们代码的复杂度,我们需要一个状态管理中心,来帮我们管理我们状态state。
- 这个时候,redux出现了,我们可以将所有的state交给redux去管理,当我们的某一个state有变化的时候,依赖到这个state的组件就会进行一次重渲染,这样就解决了我们的我们需要一直把state往下传的问题。redux有action、reducer的概念,action为唯一修改state的来源,reducer为唯一确定state如何变化的入口,这使得redux的数据流非常规范,同时也暴露出了redux代码的复杂,本来那么简单的功能,却需要完成那么多的代码。
- 后来,社区就出现了另外一套解决方案,也就是mobx,它推崇代码简约易懂,只需要定义一个可观测的对象,然后哪个组价使用到这个可观测的对象,并且这个对象的数据有更改,那么这个组件就会重渲染,而且mobx内部也做好了是否重渲染组件的生命周期shouldUpdateComponent,不建议开发者进行更改,这使得我们使用mobx开发项目的时候可以简单快速的完成很多功能,连redux的作者也推荐使用mobx进行项目开发。但是,随着项目的不断变大,mobx也不断暴露出了它的缺点,就是数据流太随意,出了bug之后不好追溯数据的流向,这个缺点正好体现出了redux的优点所在,所以针对于小项目来说,社区推荐使用mobx,对大项目推荐使用redux
调用方法时为什么需要bind this
第一次使用React的事件处理时,会有些疑惑为什么需要进行手动绑定this
。官网对 事件处理这样解释:
你必须谨慎对待 JSX 回调函数中的
this
,在 JavaScript 中,class 的方法默认不会绑定this
。如果你忘记绑定this.handleClick
并把它传入了onClick
,当你调用这个函数的时候this
的值为undefined
。这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加
()
,例如onClick={this.handleClick}
,你应该为这个方法绑定this
。
如果深究原因这主要是由于React的事件处理机制:
简单理解就是:React在组件加载(Mount)和更新(Update)时,将事件通过addEventListener
统一注册到document
上,然后会有一个事件池统一存储所有事件,当事件触发的时候,通过dispatchEvent
派发事件。
帮助我们理解为什么需要bind this
,就可以理解为:事件处理程序会被当作回调函数进行使用。
由于JavaScript的this指向问题,回调函数会丢失this指向,默认指向undifined
setState 同步还是异步
1. setState 是同步还是异步?
我的回答是执行过程代码同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,所以表现出来有时是同步,有时是“异步”。
2. 何时是同步,何时是异步呢?
只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout/setInterval等原生 API 中都是同步的。简单的可以理解为被 React 控制的函数里面就会表现出“异步”,反之表现为同步。
3. 那为什么会出现异步的情况呢?
为了做性能优化,将 state 的更新延缓到最后批量合并再去渲染对于应用的性能优化是有极大好处的,如果每次的状态改变都去重新渲染真实 dom,那么它将带来巨大的性能消耗。
React生命周期
挂载
组件首次被实例化创建并插入DOM
中需要执行的生命周期函数:
-
constructor():
需要在组件内初始化
state
或进行方法绑定时,需要定义constructor()
函数。不可在constructor()
函数中调用setState()
。 -
static getDerivedStateFromProps():
执行
getDerivedStateFromProps
函数返回我们要的更新的state
,React
会根据函数的返回值拿到新的属性。 -
render():
函数类组件必须定义的
render()
函数,是类组件中唯一必须实现的方法。render()
函数应为纯函数,也就是说只要组件state
和props
没有变化,返回的结果是相同的。其返回结果可以是:1、React
元素;2、数组或 fragments;3、Portals;4、字符串或数值类型;5、布尔值或null。不可在render()
函数中调用setState()
。 -
componentDidMount():
组件被挂载插入到
Dom
中调用此方法,可以在此方法内执行副作用操作,如获取数据,更新state
等操作。
更新
当组件的props
或state
改变时会触发更新需要执行的生命周期函数:
-
static getDerivedStateFromProps():
getDerivedStateFromProps
会在调用render
方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state
,如果返回null
则不更新任何内容。 -
shouldComponentUpdate():
根据
shouldComponentUpdate()
的返回值,判断React
组件的是否执行更新操作。React.PureComponent
就是基于浅比较进行性能优化的。一般在实际组件实践中,不建议使用该方法阻断更新过程,如果有需要建议使用React.PureComponent
。 -
render():
在组件更新阶段时如果
shouldComponentUpdate()
返回false
值时,将不执行render()
函数。 -
getSnapshotBeforeUpdate():
该生命周期函数执行在
pre-commit
阶段,此时已经可以拿到Dom
节点数据了,该声明周期返回的数据将作为componentDidUpdate()
第三个参数进行使用。 -
componentDidUpdate():
shouldComponentUpdate()
返回值false
时,则不会调用componentDidUpdate()
。
卸载
-
componentWillUnmount()
:会在组件卸载及销毁之前直接调用。一般使用此方法用于执行副作用的清理操作,如取消定时器,取消事件绑定等。
React Diff算法
React diff 作为 Virtual DOM 的加速器,其算法上的改进优化是 React 整个界面渲染的基础,以及性能提高的保障。
Diff算法并不是由React首发,Diff算法早已存在。但是传统的Diff算法,通过循环递归对比依次对比,效率低下,算法复杂度达到 O(n^3)。而React则改进Diff算法引入React。
React 分别对 tree diff、component diff 以及 element diff 进行算法优化
Tree Diff
由于Web UI 中对DOM节点的跨层级操作很少,对树进行分层比较,两棵树只会对同一层级的节点进行比较,即同一个父节点下的所有子节点。
当发现节点不存在时,则该节点及其子节点都会被删除,不会用于进一步的比较。
Component Diff
React 是基于组件构建应用的,对于组件间的比较所采取的策略也是简洁高效。
- 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
如下图,当 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。虽然当两个 component 是不同类型但结构相似时,React diff 会影响性能,但正如 React 官方博客所言:不同类型的 component 是很少存在相似 DOM tree 的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。
Element Diff
当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。
- INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。
- MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
- REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。
如下图,老集合中包含节点:A、B、C、D,更新后的新集合中包含节点:B、A、D、C,此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。
而React则Diff不会进行这种繁杂冗余操作,React则允许开发者对同一层级的同一组子节点,添加唯一key进行区分。新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,
React 的 虚拟DOM
本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述。
React实现了其对DOM节点的控制,但是DOM节点是非常复杂的,对节点的操作非常耗费资源,其实例属性非常多,对于节点操作很多冗余属性。大部分属性对于Diff操作并没有用处,所以就使用更加轻量级的虚拟DOM对DOM进行描述。
那么现在的过程就是这样:
- 维护一个使用 JS 对象表示的 Virtual DOM,与真实 DOM 一一对应
- 对前后两个 Virtual DOM 做 diff ,生成变更(Mutation)
- 把变更应用于真实 DOM,生成最新的真实 DOM
通过以上,我们发现 虚拟DOM优点是通过Diff算法减少JavaScript操作真实DOM性能消耗,但这仅仅只是其中之一的优点。
Virtual DOM更多作用
-
Virtual DOM通过牺牲了部分性能的前提下,增加了可维护性。
-
实现了对DOM的集中化操作,当数据改变时先修改Virtual DOM,再统一反映到真实DOM中,用最小的代价更新DOM,提高了效率。
-
抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器DOM中,也可以使用到安卓和IOS的原生组件。
-
打开了函数式UI编程的大门。
Virtual DOM的缺点
- 首次渲染DOM的时候,由于多一层Virtual DOM,会比
innerHTML
插入慢。 - 虚拟DOM需要在内存中维护一份DOM副本。
React 性能优化
函数组件性能优化
主要讲函数组件的性能优化方式,对类组件暂不说明。
React 函数组件优化思路主要有两个:
- 减少
render
的次数,因为在React所花的时间最多的就是进行render
- 减少计算的量。主要是减少重复计算的量,因为函数组件重新渲染时会从头开始进行函数调用。
在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate
和 PureComponent
,这两个 API 所提供的解决思路都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况。
但是我们使用函数时组件,并没有生命周期和类,我们就需要换种方式进行性能优化。
减少render
次数
通常来说,有三种原因会进行重新render
:
- 自身状态改变
- 父组件重新渲染,导致子组件重新渲染,但是父组件传递的
props
并没有发生改变。 - 父组件重新渲染,导致子组件重新渲染,但是组件传递的的
props
发生改变。
React.memo
首先要介绍的就是 React.memo
,这个 API 可以说是对标类组件里面的 PureComponent
,这是可以减少重新 render 的次数的。
我们在开发时会遇到,更改父组件状态,父组件进行重新渲染,子组件的props
并没有发生改变,但子组件依然会重新渲染。
我们就可以使用React.memo
包裹组件,如果组件Props
未发生变化的话就不会进行重新渲染。
import React from "react";
function Child(props) {
console.log(props.name)
return <h1>{props.name}</h1>
}
export default React.memo(Child)
默认情况下,React.memo
只会进行浅层比较,如果想要自己控制可以考虑,传入第二个参数,自定义进行控制。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
useCallback
当父组件传递给子组件一个函数进行调用的时候,如果父组件重新渲染,由于组件中函数会被重新调用一遍,那么前后两个传递的函数引用并不会是一个,所以 父组件传递给子组件的props发生了改变。
这种情况就可以使用useCallback
,在函数没有改变的时候,返回记忆化的函数,传递给子组件。
// index.js
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import Child from "./child";
function App() {
const [title, setTitle] = useState("这是一个 title");
const [subtitle, setSubtitle] = useState("我是一个副标题");
const callback = () => {
setTitle("标题改变了");
};
// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child
const memoizedCallback = useCallback(callback, [])
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={() => setSubtitle("副标题改变了")}>改副标题</button>
<Child onClick={memoizedCallback} name="桃桃" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
减少计算的量
useMemo
假设我们有一个函数内部每次都有一个很大的重复计算量,如果每次都是重复调用函数,那么性能会被很大的消耗。
我们可以使用useMemo
进行计算的缓存
function computeExpensiveValue() {
// 计算量很大的代码
return xxx
}
const memoizedValue = useMemo(computeExpensiveValue, [a, b]);
useMemo 的第一个参数就是一个函数,这个函数返回的值会被缓存起来,同时这个值会作为 useMemo 的返回值,第二个参数是一个数组依赖,如果数组里面的值有变化,那么就会重新去执行第一个参数里面的函数,并将函数返回的值缓存起来并作为 useMemo 的返回值 。
通用React 性能优化
懒加载组件
导入多个文件合并到一个文件中的过程叫打包,使应用不必导入大量外部文件。所有主要组件和外部依赖项都合并为一个文件,通过网络传送出去以启动并运行 Web 应用。这样可以节省大量网络调用,但这个文件会变得很大,消耗大量网络带宽。应用需要等待这个文件的加载和执行,所以传输延迟会带来严重的影响。
为了解决这个问题,我们引入代码拆分的概念。像 webpack 这样的打包器支持就支持代码拆分,它可以为应用创建多个包,并在运行时动态加载,减少初始包的大小。
import React, { lazy, Suspense } from "react";
export default class CallingLazyComponents extends React.Component {
render() {
var ComponentToLazyLoad = null;
if(this.props.name == "Mayank") {
ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
} else if(this.props.name == "Anshul") {
ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
}
return (
<div>
<h1>This is the Base User: {this.state.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<ComponentToLazyLoad />
</Suspense>
</div>
)
}
}
上面的代码中有一个条件语句,它查找 props 值,并根据指定的条件加载主组件中的两个组件。
我们可以按需懒惰加载这些拆分出来的组件,增强应用的整体性能。
不可变的数据结构
按照React的渲染规则,会有很多性能浪费在不必要的渲染上。所以我们使用Immutable
进行精确渲染,简化shouldComponentUpdate
比较。
Immutable Data
就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure
(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing
(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
Hooks
React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有,而Hooks就是解决这种问题。
为什么要使用Hooks
类组件的不足:
- 趋向复杂难以维护:在生命周期函数中混杂不相干的逻辑(如:在
componentDidMount
中注册事件以及其他的逻辑,在componentWillUnmount
中卸载事件,这样分散不集中的写法,很容易写出 bug ) - this 指向问题:父组件给子组件传递函数时,必须绑定 this
- 很难在组件之间复用状态逻辑:在组件之间复用状态逻辑很难,可能要用到 render props (渲染属性)或者 HOC(高阶组件),但无论是渲染属性,还是高阶组件,都会在原先的组件外包裹一层父容器(一般都是 div 元素),导致层级冗余
Hooks的优势:
- 优化类组件的三大问题
- 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- 副作用的关注点分离:副作用指那些没有发生在数据向视图转换过程中的逻辑,如
ajax
请求、访问原生dom
元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。、
重要钩子
- 状态钩子 (useState): 用于定义组件的 State,其到类定义中this.state的功能;
// useState 只接受一个参数: 初始状态
// 返回的是组件名和更改该组件对应的函数
const [flag, setFlag] = useState(true);
// 修改状态
setFlag(false)
// 上面的代码映射到类定义中:
this.state = {
flag: true
}
const flag = this.state.flag
const setFlag = (bool) => {
this.setState({
flag: bool,
})
}
- 生命周期钩子 (useEffect):
类定义中有许多生命周期函数,而在 React Hooks 中也提供了一个相应的函数 (useEffect),这里可以看做componentDidMount、componentDidUpdate和componentWillUnmount的结合。
useEffect(callback, [source])接受两个参数
- callback: 钩子回调函数;
- source: 设置触发条件,仅当 source 发生改变时才会触发;
- useEffect钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保存的回调中返回的函数,后再重新调用回调;
useEffect(() => {
// 组件挂载后执行事件绑定
console.log('on')
addEventListener()
// 组件 update 时会执行事件解绑
return () => {
console.log('off')
removeEventListener()
}
}, [source]);
// 每次 source 发生改变时,执行结果(以类定义的生命周期,便于大家理解):
// --- DidMount ---
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- DidUpdate ---
// 'off'
// 'on'
// --- WillUnmount ---
// 'off'
- 其它内置钩子:
-
useContext
: 获取 context 对象 -
useReducer
:类似于 Redux 思想的实现,但其并不足以替代 Redux,可以理解成一个组件内部的 redux:
- 并不是持久化存储,会随着组件被销毁而销毁;
- 属于组件内部,各个组件是相互隔离的,单纯用它并无法共享数据;
- 配合useContext`的全局性,可以完成一个轻量级的 Redux;(easy-peasy)
-
useCallback
: 缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染,具有性能优化的效果; -
useMemo
: 用于缓存传入的 props,避免依赖的组件每次都重新渲染; -
useRef
: 获取组件的真实节点; -
useLayoutEffect
:- DOM更新同步钩子。用法与useEffect类似,只是区别于执行时间点的不同
- useEffect属于异步执行,并不会等待 DOM 真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;
- 可以获取更新后的 state;
useEffect
- effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如
ajax
请求、访问原生dom
元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。 - useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API - useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
function Counter(){
let [number,setNumber] = useState(0);
let [text,setText] = useState('');
// 相当于componentDidMount 和 componentDidUpdate
useEffect(()=>{
console.log('开启一个新的定时器')
let $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
// useEffect 如果返回一个函数的话,该函数会在组件卸载和更新时调用
// useEffect 在执行副作用函数之前,会先调用上一次返回的函数
// 如果要清除副作用,要么返回一个清除副作用的函数
/* return ()=>{
console.log('destroy effect');
clearInterval($timer);
} */
});
// },[]);//要么在这里传入一个空的依赖项数组,这样就不会去重复执行
return (
<>
<input value={text} onChange={(event)=>setText(event.target.value)}/>
<p>{number}</p>
<button>+</button>
</>
)
}
useState
React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的。
通过在函数组件里调用它来给组件添加一些内部 state,React会 在重复渲染时保留这个 state
useState 唯一的参数就是初始 state
useState 会返回一个数组:一个 state,一个更新 state 的函数
- 在初始化渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
- 你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并,而是直接替换
// 这里可以任意命名,因为返回的是数组,数组解构
const [state, setState] = useState(initialState);
特点:
-
每次渲染都是一个独立的闭包:
- 每一次渲染都有它自己的 Props 和 State
- 每一次渲染都有它自己的事件处理函数
- 当点击更新状态的时候,函数组件都会重新被调用,那么每次渲染都是独立的,取到的值不会受后面操作的影响
-
函数式更新
- 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。该回调函数将接收先前的 state,并返回一个更新后的值。
-
惰性初始化state
- initialState 参数只会在组件的初始化渲染中起作用,后续渲染时会被忽略
- 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
Redux工作流程
首先,我们看下几个核心概念:
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。
- State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。
- Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
- Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。
- Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。
- dispatch:是View发出Action的唯一方法。
然后我们过下整个工作流程:
- 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
- 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
- State一旦有变化,Store就会调用监听函数,来更新View。
Vue 与 React 的区别
监听数据变化的实现原理不同
-
Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
-
React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染
为什么 React 不精确监听数据变化呢?这是因为 Vue 和 React 设计理念上的区别,Vue 使用的是可变数据,而React更强调数据的不可变。所以应该说没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。
数据流的不同
-
Vue 有双向绑定 组件 <-->DOM 数据双向
-
React提倡单向数据流,他称之为 onChange/setState()模式。
模版渲染方式不同
- React 是通过JSX渲染模板
- 而Vue是通过一种拓展的HTML语法进行渲染
在深层上,模板的原理不同,这才是他们的本质区别:
-
React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的
-
Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现
Redux 和 Vuex
从表面上来说,store注入和使用方式有一些区别。
在Vuex中,$store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store来读取数据。
在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。
React 中 keys 的作用是什么?
Keys
是React
用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识
在开发过程中,我们需要保证某个元素的 key
在其同级元素中具有唯一性。在 React Diff
算法中React
会借助元素的 Key
值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key
值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key
的重要性
Redux实现原理解析
Redux
是将整个应用状态存储到一个地方上称为store
,里面保存着一个状态树store tree
,组件可以派发(dispatch
)行为(action
)给store
,而不是直接通知其他组件,组件内部通过订阅store
中的状态state
来刷新自己的视图
参考文档
[2019年17道高频React面试题及详解](