Description
目录
-
setState原理,什么时候同步,什么时候异步
-
JSX原理,为什么自定义的React组件必须大写
-
虚拟DOM,diff算法
- 虚拟DOM是什么
- React diff原理,如何从 O(n^3)变成 O(n)
- 为什么要使用Key,key有什么好处
-
生命周期(16版本之前和之后的)
-
React事件机制
- React如何实现自己的事件机制
- React事件和原生事件有什么区别
- React 事件中为什么要绑定 this 或者 要用箭头函数, 他们有什么区别
-
fiber
-
mixin、HOC、render props、hooks
-
immber和immutable
-
受控组件和非受控组件区别
-
redux,redux 和 mobx 区别,为什么选择 redux
-
Vue和React的区别
一.setState原理,什么时候同步,什么时候异步
这里的“异步”不是说异步代码实现,而是说 React会收集变更,然后在统一更新。
在React中,如果是由React引发的事件处理(比如通过 onClick引发的事件处理),调用 setState 不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓"除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval产生的异步调用。
在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
二.JSX原理,为什么自定义的React组件必须大写
JSX原理
实际上,JSX仅仅只是 React.createElement(component, props, ...children)
函数的语法糖。如下JSX代码
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
会编译为
React.createElement(MyButton, {
color: "blue",
shadowSize: 2
}, "Click Me");
如果没有子节点,你还可以使用自闭合的标签形式,如
<div className="sidebar" />
会编译为
React.createElement("div", {
className: "sidebar"
});
可以直接在 babeljs中尝试。
为什么自定义的React组件必须大写
React.createElement(component, props, ...children)
的第一个参数 component的类型是 string/ReactClass type
- string 类型 React会当做原生的DOM节点进行解析
- ReactClass type 类型 自定义组件
简而言之,babel在编译过程中会判断 JSX 组件的首字母,如果是小写,则当做原生的DOM标签解析,就编译成字符串。如果是大写,则认为是自定义组件,编译成对象。
三. 虚拟DOM, diff算法
虚拟DOM是什么
虚拟DOM 就是使用一个 原生的JavaScript对象来描述 一个DOM节点。
<div id="wrap">
<p class="text">好好学习,天天向上</p>
</div>
使用虚拟DOM表示如下:
const element = {
// 标签名
tagName: 'div',
properties: {
id: 'wrap',
},
children: [
{
tagName: 'p',
properties: {
class: 'text',
children: ['好好学习,天天向上']
},
}
]
}
React diff原理,如何从 O(n^3)变成 O(n)
其核心是基于两个简单的假设:
- 两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
- 同一层级的一组节点,他们可以通过唯一的id进行区分。
基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)。
生命周期(16版本之前和之后的)
16版本之前的生命周期
第一次渲染
constructor -> componentWillMount -> render -> componentDidMount
props更新
componentWillReceiveProps(newProps) -> shouldComponentUpdate((nextProps, nextState))(如果返回true) -> componentUillUpdate(nextProps, nextState) -> render -> componentDidUpdate(prevProps, prevState)
state更新
shouldComponentUpdate((nextProps, nextState))(如果返回true) -> componentUillUpdate(nextProps, nextState) -> render -> componentDidUpdate(prevProps, prevState)
组件卸载
componentWillUnmount
16版本之后的生命周期
第一次渲染
constructor -> getDerivedStateFromProps(nextProps, prevState) -> render -> componentDidMount
props或state更新
getDerivedStateFromProps(nextProps, prevState) -> shouldComponentUpdate((nextProps, nextState))(如果返回true) -> render -> getSnapshotBeforeUpdate(prevProps, prevState) -> componentDidUpdate(prevProps, prevState)
组件卸载
componentWillUnmount
- React v16.3之后的组件生命周期函数
- React新生命周期--getDerivedStateFromProps
- What is getSnapshotBeforeUpdate() in React?
五.React事件机制
React事件和原生事件有什么区别
- React事件使用驼峰命名,而不是纯小写
- 通过JSX,传递一个函数作为事件处理程序,而不是一个字符串
// 传统的HTML
<button onclick="getCount()">
点击
</button>
// 在react中
<button onClick={getCount}>
点击
</button>
- 在React中,你不能通过返回 false 的方式阻止默认行为,必须显式的使用
preventDefault
;
React 事件中为什么要绑定 this 或者 要用箭头函数, 他们有什么区别
function invokeGuardedCallback(name, func, a) {
try {
func(a);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
}
}
回调函数是直接调用的,如果不手动绑定 this,获取到的 this为 undefined
六.fiber
render之前的生命周期,即将被废弃
componentWillMount
componentWillUpdate
componentWillReceiveProps
在16.x版本之前,每个生命周期在加载或更新过程中只会调用一次,因为新的fiber架构允许在 diff的时候不停的中断执行,所有render之前的声明周期可能会执行很多次。
fiber分为两部分
- Reconciliation Phase
- 进行diff,找出需要更新的DOM,这个过程是分片的。
- Commit Phase
- 更新渲染DOM,一气呵成。
js是单线程的,如果当前在执行一个很耗时的任务,那么剩下的任务就要等当前任务执行完之后再执行。16.x版本之前,React的更新过程是同步的,当React决定要更新DOM时,从diff到更新DOM,一气呵成。这种就会有一个问题,更新的组件比较复杂并且多(层级深等)的时候,此时如果用户点击了页面某个按钮,可能会因为正在批量更新DOM还未进行完成,按钮无法响应的问题。
fiber架构第一个阶段是分片的,将一个任务成很多个小的任务去执行,每次只执行一个小的任务,然后去看一下有没有优先级更高的任务,如果有,则去执行优先级更好的任务,如果没有,接着再执行下一小段任务。
为什么第二个阶段,更新渲染DOM必须是同步的呢,这个也很好理解。你总不能渲染了一半的时候去干其他的事情吧。
七. mixin、HOC、render props、hooks
mixin
vue和react中都曾用过mixin(react目前已经抛弃)
mixin(混入)本质上就是将对象复制到另一个对象上。
const mixin = function (obj, mixins) {
const newObj = obj;
newObj.prototype = Object.create(obj.prototype);
for(let prop in mixins) {
if(mixins.hasOwnProperty(prop)) {
newObj.prototype[prop] = mixins[prop];
}
}
return newObj;
}
const obj = {
sayHello() {
console.log('hello');
}
};
const otherObj = function() {
console.log('otherObj');
}
const Obj = mixin(otherObj, obj);
const a = new Obj(); // otherObj
a.sayHello(); // hello
mixin存在的几个问题:
- 相关依赖:mixin有可能去依赖其他的mixin,当我们修改其中一个的时候,可能会影响到其他的mixin
- 命名冲突:不同的人在写的时候很有可能会有命名冲突,比如像 handleChange等类似常见的名字
- 增加复杂性:当我们一个组件引入过多的mixin时,代码逻辑将会非常复杂,因为在不停的引入状态,和我们最初想的每个组件只做单一的功能背道而驰。
HOC
HOC是React社区提出的新的方式用来取代mixin的。
高阶函数是函数式编程中一个基本的概念,它描述了一种这样的函数:接受函数作为输入,或是返回一个函数,比如 map, reduce等都是高阶函数。
高阶组件( higher-order component),类似于高阶组件接受一个组件作为参数,返回另一个组件。
function getComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props}/>;
}
};
}
HOC的优点为:
- 不会影响组件内部的状态
HOC的问题是:
- 需要在原组件上进行包裹和嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难
- HOC可以劫持props,在不遵守约定的情况下也可能造成冲突
Render Props
render props: 通过props接受一个返回react element 的函数,来动态决定自己要渲染的结果
<DataProvider render={data => (
<h1>Hello {data.target}</h1>
)}/>
React Router中就用到了 Render Props
<Router>
<Route path="/home" render={() => <div>Home</div>} />
</Router>,
它有哪些问题呢
- 很容易造成“嵌套地狱”
使用 hooks
具体实现就是通过一个函数来封装跟状态有关的逻辑,将这些逻辑从组件中抽取出来。而这个函数中我们可以使用其他的Hooks,也可以单独进行测试,甚至将它贡献给社区。
import { useState, useEffect } from 'react';
function useCount() {
const [count, setCount] = useState(0);
useEffect(() = {
document.title = `You clicked ${count} times`;
});
return count
}
hooks的引入就是为了解决上面提到的这么问题,因为 使用函数式组件,我们在开发组件的时候,可以当做平常写函数一样自由。
函数复用是比较容易的,直接传不同的参数就可以渲染不同的组件,复杂组件实现,我们完全可以多封装几个函数,每个函数只单纯的负责一件事。而且很多公用的代码逻辑和一些场景我们可以抽出来,封装成自定义hooks使用,比如 Umi Hooks库封装了很多共用的逻辑,比如 useSearch,封装了异步搜索场景的逻辑;比如 useVirtualList,就封装了虚拟列表的逻辑。
八. immber和immutable
- imutable是返回的包装数据
- immer是原生数据
九.受控组件和非受控组件区别
十一. vue和React区别
相同点
- 都支持服务器端渲染
- 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponent规范
- 数据驱动视图
- 都有支持native的方案,React的React native,Vue的weex
不同点
- React严格上只针对MVC的view层,Vue则是MVVM模式
- virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
- 组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件;
- 数据绑定: vue实现了数据的双向绑定,react数据流动是单向的
- state对象在react应用中不可变的,需要使用setState方法更新状态; 在vue中,state对象不是必须的,数据由data属性在vue对象中管理