shouldComponentUpdate 有什么用?为什么它很重要?
组件状态数据或者属性数据产生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期办法 should ComponentUpdate 中,容许抉择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是依据新的状态,以最无效的形式更新用户界面。如果咱们晓得用户界面的某一部分不会扭转,那么没有理由让 React 弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate 办法中返回 false, React 将让以后组件及其所有子组件放弃与以后组件状态雷同。
React 组件生命周期有哪些不同阶段?
在组件生命周期中有四个不同的阶段:
- Initialization:在这个阶段,组件筹备设置初始化状态和默认属性。
- Mounting:react 组件曾经筹备好挂载到浏览器 DOM 中。这个阶段包含
componentWillMount
和componentDidMount
生命周期办法。 - Updating:在这个阶段,组件以两种形式更新,发送新的 props 和 state 状态。此阶段包含
shouldComponentUpdate
、componentWillUpdate
和componentDidUpdate
生命周期办法。 - Unmounting:在这个阶段,组件曾经不再被须要了,它从浏览器 DOM 中卸载下来。这个阶段蕴含
componentWillUnmount
生命周期办法。
除以上四个罕用生命周期外,还有一个错误处理的阶段:
Error Handling:在这个阶段,不管在渲染的过程中,还是在生命周期办法中或是在任何子组件的构造函数中产生谬误,该组件都会被调用。这个阶段蕴含了componentDidCatch
生命周期办法。
React 的虚构 DOM 和 Diff 算法的外部实现
传统 diff 算法的工夫复杂度是 O(n^3),这在前端 render 中是不可承受的。为了升高工夫复杂度,react 的 diff 算法做了一些斗争,放弃了最优解,最终将工夫复杂度升高到了 O(n)。
那么 react diff 算法做了哪些斗争呢?,参考如下:
- tree diff:只比照同一层的 dom 节点,疏忽 dom 节点的跨层级挪动
如下图,react 只会对雷同色彩方框内的 DOM 节点进行比拟,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被齐全删除掉,不会用于进一步的比拟。
这样只须要对树进行一次遍历,便能实现整个 DOM 树的比拟。
这就意味着,如果 dom 节点产生了跨层级挪动,react 会删除旧的节点,生成新的节点,而不会复用。
- component diff:如果不是同一类型的组件,会删除旧的组件,创立新的组件
- element diff:对于同一层级的一组子节点,须要通过惟一 id 进行来辨别
- 如果没有 id 来进行辨别,一旦有插入动作,会导致插入地位之后的列表全副从新渲染
- 这也是为什么渲染列表时为什么要应用惟一的 key。
生命周期调用办法的程序是什么?
React 生命周期分为三大周期,11 个阶段,生命周期办法调用程序别离如下。
(1)在创立期的五大阶段,调用办法的程序如下。
- getDetaultProps:定义默认属性数据。
- getInitialState:初始化默认状态数据。
- component WillMount:组件行将被构建。
- render:渲染组件。
- componentDidMount:组件构建实现
(2)在存在期的五大阶段,调用办法的程序如下。
- componentWillReceiveProps:组件行将接管新的属性数据。
- shouldComponentUpdate:判断组件是否应该更新。
- componnent WillUpdate:组件行将更新。
- render:渲染组件。
- componentDidUpdate:组件更新实现。
(3)在销毁期的一个阶段,调用办法 componentWillUnmount,示意组件行将被销毀。
在哪个生命周期中你会收回 Ajax 申请?为什么?
Ajax 申请应该写在组件创立期的第五个阶段,即 componentDidMount 生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的 5 个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate 办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀 Ajax 申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax 申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount 办法中,执行 Ajax 即可保障组件曾经挂载,并且可能失常更新组件。
为什么要应用 React. Children. map(props. children,()=>)而不是 props. children. map (() => )?
因为不能保障 props. children 将是一个数组。
以上面的代码为例。
<Parent>
<h1> 有课前端网 </h1>
</Parent>
在父组件外部,如果尝试应用 props.children. map 映射子对象,则会抛出谬误,因为 props. children 是一个对象,而不是一个数组。
如果有多个子元素,React 会使 props.children 成为一个数组,如下所示。
<Parent>
<h1> 有课前端网 </h1>
<h2> 前端技术学习平台 </h2>
</Parent>;
// 不倡议应用如下形式,在这个案例中会抛出谬误。class Parent extends Component {render() {return <div> {this.props.children.map((obj) => obj)}</div>;
}
}
倡议应用如下形式,防止在上一个案例中抛出谬误。
class Parent extends Component {render() {return <div> {React.Children.map(this.props.children, (obj) => obj)}</div>;
}
}
参考 前端进阶面试题具体解答
react diff 算法
咱们晓得 React 会保护两个虚构 DOM,那么是如何来比拟,如何来判断,做出最优的解呢?这就用到了 diff 算法
diff 算法的作用
计算出 Virtual DOM 中真正变动的局部,并只针对该局部进行原生 DOM 操作,而非从新渲染整个页面。
传统 diff 算法
通过循环递归对节点进行顺次比照,算法复杂度达到
O(n^3)
,n 是树的节点数,这个有多可怕呢?——如果要展现 1000 个节点,得执行上亿次比拟。。即使是 CPU 快能执行 30 亿条命令,也很难在一秒内计算出差别。
React 的 diff 算法
- 什么是和谐?
将 Virtual DOM 树转换成 actual DOM 树的起码操作的过程 称为 和谐。
- 什么是 React diff 算法?
diff
算法是和谐的具体实现。
diff 策略
React 用 三大策略 将
O(n^3)
杂度 转化为O(n)
复杂度
策略一(tree diff):
- Web UI 中 DOM 节点跨层级的挪动操作特地少,能够忽略不计
- 同级比拟, 既然 DOM 节点跨层级的挪动操作少到能够忽略不计,那么 React 通过 updateDepth 对 Virtual DOM 树进行层级管制,也就是同一层,在比照的过程中,如果发现节点不在了,会齐全删除不会对其余中央进行比拟,这样只须要对树遍历一次就 OK 了
策略二(component diff):
- 领有雷同类的两个组件 生成类似的树形构造,
- 领有不同类的两个组件 生成不同的树形构造。
策略三(element diff):
对于同一层级的一组子节点,通过惟一 id 辨别。
tree diff
- React 通过 updateDepth 对 Virtual DOM 树进行层级管制。
- 对树分层比拟,两棵树 只对同一档次节点 进行比拟。如果该节点不存在时,则该节点及其子节点会被齐全删除,不会再进一步比拟。
- 只需遍历一次,就能实现整棵 DOM 树的比拟。
那么问题来了,如果 DOM 节点呈现了跨层级操作,diff 会咋办呢?
答:diff 只简略思考同层级的节点地位变换,如果是跨层级的话,只有创立节点和删除节点的操作。
如上图所示,以 A 为根节点的整棵树会被从新创立,而不是挪动,因而 官网倡议不要进行 DOM 节点跨层级操作,能够通过 CSS 暗藏、显示节点,而不是真正地移除、增加 DOM 节点
component diff
React 对不同的组件间的比拟,有三种策略
- 同一类型的两个组件,按原策略(层级比拟)持续比拟 Virtual DOM 树即可。
- 同一类型的两个组件,组件 A 变动为组件 B 时,可能 Virtual DOM 没有任何变动,如果晓得这点(变换的过程中,Virtual DOM 没有扭转),可节俭大量计算工夫,所以 用户 能够通过
shouldComponentUpdate()
来判断是否须要 判断计算。 - 不同类型的组件,将一个(将被扭转的)组件判断为
dirty component
(脏组件),从而替换 整个组件的所有节点。
留神:如果组件 D 和组件 G 的构造类似,然而 React 判断是 不同类型的组件,则不会比拟其构造,而是删除 组件 D 及其子节点,创立组件 G 及其子节点。
element diff
当节点处于同一层级时,diff 提供三种节点操作:删除、插入、挪动。
- 插入:组件 C 不在汇合(A,B)中,须要插入
-
删除:
- 组件 D 在汇合(A,B,D)中,但 D 的节点曾经更改,不能复用和更新,所以须要删除 旧的 D,再创立新的。
- 组件 D 之前在 汇合(A,B,D)中,但汇合变成新的汇合(A,B)了,D 就须要被删除。
- 挪动:组件 D 曾经在汇合(A,B,C,D)里了,且汇合更新时,D 没有产生更新,只是地位扭转,如新汇合(A,D,B,C),D 在第二个,毋庸像传统 diff,让旧汇合的第二个 B 和新汇合的第二个 D 比拟,并且删除第二个地位的 B,再在第二个地位插入 D,而是(对同一层级的同组子节点)增加惟一 key 进行辨别,挪动即可。
diff 的有余与待优化的中央
尽量减少相似将最初一个节点挪动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响 React 的渲染性能
在 ReactNative 中,如何解决 adb devices 找不到连贯设施的问题?
在应用 Genymotion 时,首先须要在 SDK 的 platform-tools 中退出环境变量,而后在 Genymotion 中单击 Setting,抉择 ADB 选项卡,单击 Use custom Android SDK tools,浏览本地 SDK 的地位,单击 OK 按钮就能够了。启动虛拟机后,在 cmd 中输出 adb devices 能够查看设施。
调用 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 元素
ref 是一个函数又有什么益处?
- 不便 react 销毁组件、从新渲染的时候去清空 refs 的货色,避免内存泄露
为什么虚构 dom 会进步性能
虚构
dom
相当于在js
和实在dom
两头加了一个缓存,利用dom diff
算法防止了没有必要的dom
操作,从而进步性能
具体实现步骤如下
- 用
JavaScript
对象构造示意 DOM 树的构造;而后用这个树构建一个真正的DOM
树,插到文档当中 - 当状态变更的时候,从新结构一棵新的对象树。而后用新的树和旧的树进行比拟,记录两棵树差别
- 把 2 所记录的差别利用到步骤 1 所构建的真正的
DOM
树上,视图就更新
虚构 DOM 肯定会进步性能吗?
很多人认为虚构 DOM 肯定会进步性能,肯定会更快,其实这个说法有点全面,因为虚构 DOM 尽管会缩小 DOM 操作,但也无奈防止 DOM 操作
- 它的劣势是在于 diff 算法和批量解决策略, 将所有的 DOM 操作收集起来,一次性去扭转实在的 DOM, 但在首次渲染上,虚构 DOM 会多了一层计算,耗费一些性能,所以有可能会比 html 渲染的要慢
- 留神,虚构 DOM 实际上是给咱们找了一条最短,最近的门路,并不是说比 DOM 操作的更快,而是门路最简略
这段代码有什么问题?
class App extends Component {constructor(props) {super(props);
this.state = {
username: "有课前端网",
msg: " ",
};
}
render() {return <div> {this.state.msg}</div>;
}
componentDidMount() {this.setState((oldState, props) => {
return {msg: oldState.username + "-" + props.intro,};
});
}
}
render (< App intro=” 前端技术业余学习平台 ”></App>,ickt)
在页面中失常输入“有课前端网 - 前端技术业余学习平台”。然而这种写法很少应用,并不是罕用的写法。React 容许对 setState 办法传递一个函数,它接管到先前的状态和属性数据并返回一个须要批改的状态对象,正如咱们在下面所做的那样。它岂但没有问题,而且如果依据以前的状态(state)以及属性来批改以后状态,举荐应用这种写法。
传入 setstate 函数的第二个参数的作用是什么?
第二个参数是一个函数,该函数会在 setState 函数调用实现并且组件开始重渲染时调用,能够用该函数来监听渲染是否实现。
this.setstate(
{username: "有课前端网",},
() => console.log("re-rendered success.")
);
react-router 里的 <Link>
标签和 <a>
标签有什么区别
比照
<a>
,Link
组件防止了不必要的重渲染
React 怎么做数据的检查和变动
Model
扭转之后(可能是调用了setState
),触发了virtual dom
的更新,再用diff
算法来把 virtual DOM
比拟real DOM
,看看是哪个dom
节点更新了,再渲染real dom
高阶组件存在的问题
- 静态方法失落(必须将静态方法做拷贝)
refs
属性不能透传 (如果你向一个由高阶组件创立的组件的元素增加ref
援用,那么ref
指向的是最外层容器组件实例的,而不是被包裹的WrappedComponent
组件。)- 反向继承不能保障残缺的子组件树被解析
React 组件有两种模式,别离是 class 类型和 function 类型(无状态组件)。
咱们晓得反向继承的渲染劫持能够管制 WrappedComponent
的渲染过程,也就是说这个过程中咱们能够对 elements tree
、state
、props
或 render()
的后果做各种操作。
然而如果渲染 elements tree
中蕴含了 function 类型的组件的话,这时候就不能操作组件的子组件了。
React 中什么是受控组件和非控组件?
(1)受控组件 在应用表单来收集用户输出时,例如<input><select><textearea>
等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件,更新组件的 state。这种组件在 React 中被称为 受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性绝对应,react 通过这种形式打消了组件的部分状态,使整个状态可控。react 官网举荐应用受控表单组件。
受控组件更新 state 的流程:
- 能够通过初始 state 中设置表单的默认值
- 每当表单的值发生变化时,调用 onChange 事件处理器
- 事件处理器通过事件对象 e 拿到扭转后的状态,并更新组件的 state
- 一旦通过 setState 办法更新 state,就会触发视图的从新渲染,实现表单组件的更新
受控组件缺点: 表单元素的值都是由 React 组件进行治理,当有多个输入框,或者多个这种组件时,如果想同时获取到全副的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种状况,呈现了非受控组件。
(2)非受控组件 如果一个表单组件没有 value props(单选和复选按钮对应的是 checked props)时,就能够称为非受控组件。在非受控组件中,能够应用一个 ref 来从 DOM 取得表单值。而不是为每个状态更新编写一个事件处理程序。
React 官网的解释:
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你能够应用 ref 来从 DOM 节点中获取表单数据。
因为非受控组件将实在数据贮存在 DOM 节点中,所以在应用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码好看性,并且心愿疾速编写代码,应用非受控组件往往能够缩小你的代码量。否则,你应该应用受控组件。
例如,上面的代码在非受控组件中接管单个属性:
class NameForm extends React.Component {constructor(props) {super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {alert('A name was submitted:' + this.input.value);
event.preventDefault();}
render() {
return (<form onSubmit={this.handleSubmit}>
<label>
Name: <input type="text" ref={(input) => this.input = input} /> </label>
<input type="submit" value="Submit" />
</form>
);
}
}
总结: 页面中所有输出类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输出的值保护到了 state 中,须要时再从 state 中取出,这里的数据就受到了 state 的管制,称为受控组件。
父子组件的通信形式?
父组件向子组件通信:父组件通过 props 向子组件传递须要的信息。
// 子组件: Child
const Child = props =>{return <p>{props.name}</p>
}
// 父组件 Parent
const Parent = ()=>{return <Child name="react"></Child>}
子组件向父组件通信:: props+ 回调的形式。
// 子组件: Child
const Child = props =>{
const cb = msg =>{return ()=>{props.callback(msg)
}
}
return (<button onClick={cb("你好!")}> 你好 </button>
)
}
// 父组件 Parent
class Parent extends Component {callback(msg){console.log(msg)
}
render(){return <Child callback={this.callback.bind(this)}></Child>
}
}
React- Router 有几种模式?
有以下几种模式。
HashRouter,通过散列实现,路由要带 #。
BrowerRouter,利用 HTML5 中 history API 实现,须要服务器端反对,兼容性不是很好。
什么是高阶组件(HOC)
- 高阶组件 (Higher Order Componennt) 自身其实不是组件,而是一个函数,这个函数接管一个元组件作为参数,而后返回一个新的加强组件,高阶组件的呈现自身也是为了逻辑复用,举个例子
function withLoginAuth(WrappedComponent) {
return class extends React.Component {constructor(props) {super(props);
this.state = {isLogin: false};
}
async componentDidMount() {const isLogin = await getLoginStatus();
this.setState({isLogin});
}
render() {if (this.state.isLogin) {return <WrappedComponent {...this.props} />;
}
return (<div> 您还未登录...</div>);
}
}
}