共计 9497 个字符,预计需要花费 24 分钟才能阅读完成。
概述下 React 中的事件处理逻辑
- 抹平浏览器差别,实现更好的跨平台。
- 防止垃圾回收,React 引入事件池,在事件池中获取或开释事件对象,防止频繁地去创立和销毁。
- 不便事件对立治理和事务机制。
为了解决跨浏览器兼容性问题,
React
会将浏览器原生事件(Browser Native Event
)封装为合成事件(SyntheticEvent
)传入设置的事件处理器中。这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外有意思的是,React
并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决。这样React
在更新DOM
的时候就不须要思考如何去解决附着在DOM
上的事件监听器,最终达到优化性能的目标
React 最新的⽣命周期是怎么的?
React 16 之后有三个⽣命周期被废除(但并未删除)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
官⽅打算在 17 版本齐全删除这三个函数,只保留 UNSAVE_前缀的三个函数,⽬的是为了向下兼容,然而对于开发者⽽⾔应该尽量避免使⽤他们,⽽是使⽤新增的⽣命周期函数代替它们。
⽬前 React16.8+ 的⽣命周期分为三个阶段,别离是挂载阶段、更新阶段、卸载阶段。
挂载阶段:
- constructor:构造函数,最先被执⾏,咱们通常在构造函数⾥初始化 state 对象或者给⾃定义⽅法绑定 this;
- getDerivedStateFromProps:static getDerivedStateFromProps(nextProps, prevState),这是个动态⽅法,当咱们接管到新的属性想去批改咱们 state,能够使⽤ getDerivedStateFromProps
- render:render 函数是纯函数,只返回须要渲染的东⻄,不应该蕴含其它的业务逻辑,能够返回原⽣的 DOM、React 组件、Fragment、Portals、字符串和数字、Boolean 和 null 等内容;
- componentDidMount:组件装载之后调⽤,此时咱们能够获取到 DOM 节点并操作,⽐如对 canvas,svg 的操作,服务器申请,订阅都能够写在这个⾥⾯,然而记得在 componentWillUnmount 中勾销订阅;
更新阶段:
- getDerivedStateFromProps: 此⽅法在更新个挂载阶段都可能会调⽤;
- shouldComponentUpdate:shouldComponentUpdate(nextProps, nextState),有两个参数 nextProps 和 nextState,示意新的属性和变动之后的 state,返回⼀个布尔值,true 示意会触发从新渲染,false 示意不会触发从新渲染,默认返回 true,咱们通常利⽤此⽣命周期来优化 React 程序性能;
- render:更新阶段也会触发此⽣命周期;
- getSnapshotBeforeUpdate:getSnapshotBeforeUpdate(prevProps, prevState), 这个⽅法在 render 之后,componentDidUpdate 之前调⽤,有两个参数 prevProps 和 prevState,示意之前的属性和之前的 state,这个函数有⼀个返回值,会作为第三个参数传给 componentDidUpdate,如果你不想要返回值,能够返回 null,此⽣命周期必须与 componentDidUpdate 搭配使⽤;
- componentDidUpdate:componentDidUpdate(prevProps, prevState, snapshot),该⽅法在 getSnapshotBeforeUpdate ⽅法之后被调⽤,有三个参数 prevProps,prevState,snapshot,示意之前的 props,之前的 state,和 snapshot。第三个参数是 getSnapshotBeforeUpdate 返回的,如果触发某些回调函数时须要⽤到 DOM 元素的状态,则将对⽐或计算的过程迁徙⾄ getSnapshotBeforeUpdate,而后在 componentDidUpdate 中统⼀触发回调或更新状态。
卸载阶段:
-componentWillUnmount:当咱们的组件被卸载或者销毁了就会调⽤,咱们能够在这个函数⾥去革除⼀些定时器,勾销⽹络申请,清理⽆效的 DOM 元素等垃圾清理⼯作。
总结:
- componentWillMount:在渲染之前执行,用于根组件中的 App 级配置;
- componentDidMount:在第一次渲染之后执行,能够在这里做 AJAX 申请,DOM 的操作或状态更新以及设置事件监听器;
- componentWillReceiveProps:在初始化 render 的时候不会执行,它会在组件承受到新的状态 (Props) 时被触发,个别用于父组件状态更新时子组件的从新渲染
- shouldComponentUpdate:确定是否更新组件。默认状况下,它返回 true。如果确定在 state 或 props 更新后组件不须要在从新渲染,则能够返回 false,这是一个进步性能的办法;
- componentWillUpdate:在 shouldComponentUpdate 返回 true 确定要更新组件之前件之前执行;
- componentDidUpdate:它次要用于更新 DOM 以响应 props 或 state 更改;
- componentWillUnmount:它用于勾销任何的网络申请,或删除与组件关联的所有事件监听器。
React 中的 key 是什么?为什么它们很重要?
key 能够帮忙 React 跟踪循环创立列表中的虚构 DOM 元素,理解哪些元素已更改、增加或删除。
每个绑定 key 的虚构 DOM 元素,在兄弟元素之间都是举世无双的。在 React 的和解过程中,比拟新的虛拟 DOM 树与上一个虛拟 DOM 树之间的差别,并映射到页面中。key 使 React 解决列表中虛拟 DOM 时更加高效,因为 React 能够应用虛拟 DOM 上的 key 属性,疾速理解元素是新的、须要删除的,还是批改过的。如果没有 key,Rεat 就不晓得列表中虚构 DOM 元素与页面中的哪个元素绝对应。所以在创立列表的时候,不要疏忽 key。
形容事件在 React 中的解决形式。
为了解决跨浏览器兼容性问题,React 中的事件处理程序将传递 SyntheticEvent 的实例,它是跨浏览器事件的包装器。这些 SyntheticEvent 与你习惯的原生事件具备雷同的接口,它们在所有浏览器中都兼容。
React 实际上并没有将事件附加到子节点自身。而是通过事件委托模式,应用单个事件监听器监听顶层的所有事件。这对于性能是有益处的。这也意味着在更新 DOM 时,React 不须要放心跟踪事件监听器。
调用 setState 之后产生了什么
在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,而后触发所谓的和谐过程(Reconciliation)。通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面。在 React 失去元素树之后,React 会计算出新的树和老的树之间的差别,而后依据差别对界面进行最小化从新渲染。通过 diff 算法,React 可能准确制导哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。
- 在 setState 的时候,React 会为以后节点创立一个 updateQueue 的更新列队。
- 而后会触发 reconciliation 过程,在这个过程中,会应用名为 Fiber 的调度算法,开始生成新的 Fiber 树,Fiber 算法的最大特点是能够做到异步可中断的执行。
- 而后 React Scheduler 会依据优先级高下,先执行优先级高的节点,具体是执行 doWork 办法。
- 在 doWork 办法中,React 会执行一遍 updateQueue 中的办法,以取得新的节点。而后比照新旧节点,为老节点打上 更新、插入、替换 等 Tag。
- 以后节点 doWork 实现后,会执行 performUnitOfWork 办法取得新节点,而后再反复下面的过程。
- 当所有节点都 doWork 实现后,会触发 commitRoot 办法,React 进入 commit 阶段。
- 在 commit 阶段中,React 会依据后面为各个节点打的 Tag,一次性更新整个 dom 元素
生命周期调用办法的程序是什么?
React 生命周期分为三大周期,11 个阶段,生命周期办法调用程序别离如下。
(1)在创立期的五大阶段,调用办法的程序如下。
- getDetaultProps:定义默认属性数据。
- getInitialState:初始化默认状态数据。
- component WillMount:组件行将被构建。
- render:渲染组件。
- componentDidMount:组件构建实现
(2)在存在期的五大阶段,调用办法的程序如下。
- componentWillReceiveProps:组件行将接管新的属性数据。
- shouldComponentUpdate:判断组件是否应该更新。
- componnent WillUpdate:组件行将更新。
- render:渲染组件。
- componentDidUpdate:组件更新实现。
(3)在销毁期的一个阶段,调用办法 componentWillUnmount,示意组件行将被销毀。
参考 前端进阶面试题具体解答
如何通知 React 它应该编译生产环境版
通常状况下咱们会应用
Webpack
的DefinePlugin
办法来将NODE_ENV
变量值设置为production
。编译版本中React
会疏忽propType
验证以及其余的告警信息,同时还会升高代码库的大小,React
应用了Uglify
插件来移除生产环境下不必要的正文等信息
setState 到底是异步还是同步?
先给出答案: 有时体现出异步, 有时体现出同步
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的setState
的“异步”并不是说外部由异步代码实现,其实自身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用程序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,造成了所谓的“异步”,当然能够通过第二个参数setState(partialState, callback)
中的callback
拿到更新后的后果setState
的批量更新优化也是建设在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout
中不会批量更新,在“异步”中如果对同一个值进行屡次setState
,setState
的批量更新策略会对其进行笼罩,取最初一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新
react 性能优化是哪个周期函数
shouldComponentUpdate
这个办法用来判断是否须要调用 render 办法从新描述 dom。因为 dom 的描述十分耗费性能,如果咱们能在shouldComponentUpdate 方
法中可能写出更优化的dom diff
算法,能够极大的进步性能
redux 的三大准则
-
繁多数据源
整个利用的 state 被存储在一个 object tree 中,并且这个 object tree 之存在惟一一个 store 中
-
state 是只读的
惟一扭转 state 的形式是触发 action,action 是一个用于形容曾经产生工夫的对象,这个保障了视图和网络申请都不能间接批改 state,相同他们只能表白想要批改的用意
- 应用纯函数来执行批改 state
为了形容 action 如何扭转 state tree 须要编写 reduce
请说岀 React 从 EMAScript5 编程标准到 EMAScript6 编程标准过程中的几点扭转。
次要扭转如下。
(1)创立组件的办法不同。
EMAScript5 版本中,定义组件用 React.createClass。EMAScript6 版本中,定义组件要定义组件类,并继承 Component 类。
(2)定义默认属性的办法不同。
EMAScript5 版本中,用 getDefaultProps 定义默认属性。EMAScript6 版本中,为组件定义 defaultProps 动态属性,来定义默认属性。
(3)定义初始化状态的办法不同。EMAScript5 版本中,用 getInitialState 定义初始化状态。EMAScript6 版本中,在构造函数中,通过 this. state 定义初始化状态。
留神:构造函数的第一个参数是属性数据,肯定要用 super 继承。
(4)定义属性束缚的办法不同。
EMAScript5 版本中,用 propTypes 定义属性的束缚。
EMAScript6 版本中,为组件定义 propsTypes 动态属性,来对属性进行束缚。
(5)应用混合对象、混合类的办法不同。
EMAScript5 版本中,通过 mixins 继承混合对象的办法。
EMAScript6 版本中,定义混合类,让混合类继承 Component 类,而后让组件类继承混合类,实现对混合类办法的继承。
(6)绑定事件的办法不同。
EMAScript5 版本中,绑定的事件回调函数作用域是组件实例化对象。
EMAScript6 版本中,绑定的事件回调函数作用域是 null。
(7)父组件传递办法的作用域不同。
EMAScript5 版本中,作用域是父组件。EMAScript6 版本中,变成了 null。
(8)组件办法作用域的批改办法不同。
EMAScript5 版本中,无奈扭转作用域。
EMAScript6 版本中,作用域是能够扭转的。
setState 到底是异步还是同步?
先给出答案: 有时体现出异步,有时体现出同步。
- setState 只在合成事件和钩⼦函数中是“异步”的,在原⽣事件和 setTimeout 中都是同步的;
- setState 的“异步”并不是说外部由异步代码实现,其实自身执⾏的过程和代码都是同步的,只是合成事件和钩⼦函数的调⽤程序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到更新后的值,造成了所谓的“异步”,当然能够通过第⼆个参数 setState(partialState, callback)中的 callback 拿到更新后的后果;
- setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事件和 setTimeout 中不会批量更新,在“异步”中如果对同⼀个值进⾏屡次 setState,setState 的批量更新策略会对其进⾏笼罩,取最初⼀次的执⾏,如果是同时 setState 多个不同的值,在更新时会对其进⾏合并批量更新。
在生命周期中的哪一步你应该发动 AJAX 申请
咱们该当将 AJAX 申请放到
componentDidMount
函数中执行,次要起因有下
React
下一代和谐算法Fiber
会通过开始或进行渲染的形式优化利用性能,其会影响到componentWillMount
的触发次数。对于componentWillMount
这个生命周期函数的调用次数会变得不确定,React
可能会屡次频繁调用componentWillMount
。如果咱们将AJAX
申请放到componentWillMount
函数中,那么不言而喻其会被触发屡次,天然也就不是好的抉择。- 如果咱们将
AJAX
申请搁置在生命周期的其余函数中,咱们并不能保障申请仅在组件挂载结束后才会要求响应。如果咱们的数据申请在组件挂载之前就实现,并且调用了setState
函数将数据增加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount
函数中进行AJAX
申请则能无效防止这个问题
React 中 Diff 算法的原理是什么?
原理如下。
(1)节点之间的比拟。
节点包含两种类型:一种是 React 组件,另一种是 HTML 的 DOM。
如果节点类型不同,按以下形式比拟。
如果 HTML DOM 不同,间接应用新的替换旧的。如果组件类型不同,也间接应用新的替换旧的。
如果 HTML DOM 类型雷同,按以下形式比拟。
在 React 里款式并不是一个纯正的字符串,而是一个对象,这样在款式产生扭转时,只须要扭转替换变动当前的款式。批改完以后节点之后,递归解决该节点的子节点。
如果组件类型雷同,按以下形式比拟。
如果组件类型雷同,应用 React 机制解决。个别应用新的 props 替换旧的 props,并在之后调用组件的 componentWillReceiveProps 办法,之前组件的 render 办法会被调用。
节点的比拟机制开始递归作用于它的子节点。
(2)两个列表之间的比拟。
一个节点列表中的一个节点产生扭转,React 无奈很妤地解决这个问题。循环新旧两个列表,并找出不同,这是 React 惟一的解决办法。
然而,有一个方法能够把这个算法的复杂度升高。那就是在生成一个节点列表时给每个节点上增加一个 key。这个 key 只须要在这一个节点列表中惟一,不须要全局惟一。
(3)取舍
须要留神的是,下面的启发式算法基于两点假如。
类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树
能够为屡次 render 都体现稳固的节点设置 key。
下面的节点之间的比拟算法基本上就是基于这两个假如而实现的。要进步 React 利用的效率,须要依照这两点假如来开发。
应用箭头函数 (arrow functions) 的长处是什么
- 作用域平安:在箭头函数之前,每一个新创建的函数都有定义本身的
this
值(在构造函数中是新对象;在严格模式下,函数调用中的this
是未定义的;如果函数被称为“对象办法”,则为根底对象等),但箭头函数不会,它会应用关闭执行上下文的this
值。 - 简略:箭头函数易于浏览和书写
- 清晰:当一切都是一个箭头函数,任何惯例函数都能够立刻用于定义作用域。开发者总是能够查找 next-higher 函数语句,以查看
this
的值
高阶组件的利用场景
权限管制
利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度: 页面级别 和 页面元素级别
// HOC.js
function withAdminAuth(WrappedComponent) {
return class extends React.Component {
state = {isAdmin: false,}
async componentWillMount() {const currentRole = await getCurrentUserRole();
this.setState({isAdmin: currentRole === 'Admin',});
}
render() {if (this.state.isAdmin) {return <WrappedComponent {...this.props} />;
} else {return (<div> 您没有权限查看该页面,请分割管理员!</div>);
}
}
};
}
// 应用
// pages/page-a.js
class PageA extends React.Component {constructor(props) {super(props);
// something here...
}
componentWillMount() {// fetching data}
render() {// render page with data}
}
export default withAdminAuth(PageA);
可能你曾经发现了,高阶组件其实就是装璜器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数外部对该组件(函数或类)进行性能的加强(不批改传入参数的前提下),最初返回这个组件(函数或类),即容许向一个现有的组件增加新的性能,同时又不去批改该组件,属于 包装模式(Wrapper Pattern) 的一种。
什么是装璜者模式:在不扭转对象本身的前提下在程序运行期间动静的给对象增加一些额定的属性或行为
能够进步代码的复用性和灵活性。
再对高阶组件进行一个小小的总结:
- 高阶组件 不是组件 , 是 一个把某个组件转换成另一个组件的 函数
- 高阶组件的次要作用是 代码复用
- 高阶组件是 装璜器模式在 React 中的实现
封装组件的准则
封装准则
1、繁多准则:负责繁多的页面渲染
2、多重职责:负责多重职责,获取数据,复用逻辑,页面渲染等
3、明确承受参数:必选,非必选,参数尽量设置以_结尾,防止变量反复
4、可扩大:需要变动可能及时调整,不影响之前代码
5、代码逻辑清晰
6、封装的组件必须具备高性能,低耦合的个性
7、组件具备繁多职责:封装业务组件或者根底组件,如果不能给这个组件起一个有意义的名字,证实这个组件承当的职责可能不够繁多,须要持续抽组件,直到它能够是一个独立的组件即可
react 旧版生命周期函数
初始化阶段
getDefaultProps
: 获取实例的默认属性getInitialState
: 获取每个实例的初始化状态componentWillMount
:组件行将被装载、渲染到页面上render
: 组件在这里生成虚构的DOM
节点componentDidMount
: 组件真正在被装载之后
运行中状态
componentWillReceiveProps
: 组件将要接管到属性的时候调用shouldComponentUpdate
: 组件承受到新属性或者新状态的时候(能够返回 false,接收数据后不更新,阻止render
调用,前面的函数不会被继续执行了)componentWillUpdate
: 组件行将更新不能批改属性和状态render
: 组件从新描述componentDidUpdate
: 组件曾经更新
销毁阶段
componentWillUnmount
: 组件行将销毁
如何有条件地向 React 组件增加属性?
对于某些属性,React 十分聪慧,如果传递给它的值是虚值,能够省略该属性。例如:
var InputComponent = React.createClass({render: function () {
var required = true;
var disabled = false;
return <input type="text" disabled={disabled} required={required} />;
},
});
渲染后果:
<input type="text" required>
另一种可能的办法是:
var condition = true;
var component = <div value="foo" {...(condition && { disabled: true})} />;
为什么不间接更新 state
呢 ?
如果试图间接更新 state
,则不会从新渲染组件。
// 谬误
This.state.message = 'Hello world';
须要应用 setState()
办法来更新 state
。它调度对组件 state
对象的更新。当 state
扭转时,组件通过从新渲染来响应:
// 正确做法
This.setState({message:‘Hello World’});
hooks 为什么不能放在条件判断里
以 setState 为例,在 react 外部,每个组件 (Fiber) 的 hooks 都是以链表的模式存在 memoizeState 属性中
update 阶段,每次调用 setState,链表就会执行 next 向后挪动一步。如果将 setState 写在条件判断中,假如条件判断不成立,没有执行外面的 setState 办法,会导致接下来所有的 setState 的取值呈现偏移,从而导致异样产生。