为什么调用 setState 而不是间接扭转 state?
解答
如果您尝试间接扭转组件的状态,React 将无奈得悉它须要从新渲染组件。通过应用 setState()
办法,React 能够更新组件的 UI。
另外,您还能够谈谈如何不保障状态更新是同步的。如果须要基于另一个状态(或属性)更新组件的状态,请向 setState()
传递一个函数,该函数将 state 和 props 作为其两个参数:
this.setState((state, props) => ({counter: state.counter + props.increment}));
React 实现的挪动利用中,如果呈现卡顿,有哪些能够思考的优化计划
- 减少
shouldComponentUpdate
钩子对新旧props
进行比拟,如果值雷同则阻止更新,防止不必要的渲染,或者应用PureReactComponent
代替Component
,其外部曾经封装了shouldComponentUpdate
的浅比拟逻辑 - 对于列表或其余构造雷同的节点,为其中的每一项减少惟一
key
属性,以不便React
的diff
算法中对该节点的复用,缩小节点的创立和删除操作 render
函数中缩小相似onClick={() => {doSomething()}}
的写法,每次调用 render 函数时均会创立一个新的函数,即便内容没有产生任何变动,也会导致节点没必要的重渲染,倡议将函数保留在组件的成员对象中,这样只会创立一次- 组件的
props
如果须要通过一系列运算后能力拿到最终后果,则能够思考应用reselect
库对后果进行缓存,如果 props 值未发生变化,则后果间接从缓存中拿,防止昂扬的运算代价 webpack-bundle-analyzer
剖析以后页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化
React key 是干嘛用的 为什么要加?key 次要是解决哪一类问题的
Keys 是 React 用于追踪哪些列表中元素被批改、被增加或者被移除的辅助标识。在开发过程中,咱们须要保障某个元素的 key 在其同级元素中具备唯一性。
在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是早先创立的还是被挪动而来的元素,从而缩小不必要的元素重渲染此外,React 还须要借助 Key 值来判断元素与本地状态的关联关系。
注意事项:
- key 值肯定要和具体的元素—一对应;
- 尽量不要用数组的 index 去作为 key;
- 不要在 render 的时候用随机数或者其余操作给元素加上不稳固的 key,这样造成的性能开销比不加 key 的状况下更蹩脚。
React 组件中怎么做事件代理?它的原理是什么?
React 基于 Virtual DOM 实现了一个 SyntheticEvent 层(合成事件层),定义的事件处理器会接管到一个合成事件对象的实例,它合乎 W3C 规范,且与原生的浏览器事件领有同样的接口,反对冒泡机制,所有的事件都主动绑定在最外层上。
在 React 底层,次要对合成事件做了两件事:
- 事件委派: React 会把所有的事件绑定到构造的最外层,应用对立的事件监听器,这个事件监听器上维持了一个映射来保留所有组件外部事件监听和处理函数。
- 主动绑定: React 组件中,每个办法的上下文都会指向该组件的实例,即主动绑定 this 为以后组件。
参考:前端 react 面试题具体解答
在 React 中如何防止不必要的 render?
React 基于虚构 DOM 和高效 Diff 算法的完满配合,实现了对 DOM 最小粒度的更新。大多数状况下,React 对 DOM 的渲染效率足以业务日常。但在个别简单业务场景下,性能问题仍然会困扰咱们。此时须要采取一些措施来晋升运行性能,其很重要的一个方向,就是防止不必要的渲染(Render)。这里提下优化的点:
- shouldComponentUpdate 和 PureComponent
在 React 类组件中,能够利用 shouldComponentUpdate 或者 PureComponent 来缩小因父组件更新而触发子组件的 render,从而达到目标。shouldComponentUpdate 来决定是否组件是否从新渲染,如果不心愿组件从新渲染,返回 false 即可。
- 利用高阶组件
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,能够利用高阶组件,封装一个相似 PureComponet 的性能
- 应用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,防止不必要的更新,其实也是一个高阶组件,与 PureComponent 非常相似,但不同的是,React.memo 只能用于函数组件。
React setState 调用的原理
具体的执行过程如下(源码级解析):
- 首先调用了
setState
入口函数,入口函数在这里就是充当一个散发器的角色,依据入参的不同,将其散发到不同的性能函数中去;
ReactComponent.prototype.setState = function (partialState, callback) {this.updater.enqueueSetState(this, partialState);
if (callback) {this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueSetState
办法将新的state
放进组件的状态队列里,并调用enqueueUpdate
来解决将要更新的实例对象;
enqueueSetState: function (publicInstance, partialState) {
// 依据 this 拿到对应的组件实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 这个 queue 对应的就是一个组件实例的 state 数组
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// enqueueUpdate 用来解决以后的组件实例
enqueueUpdate(internalInstance);
}
- 在
enqueueUpdate
办法中引出了一个要害的对象——batchingStrategy
,该对象所具备的isBatchingUpdates
属性间接决定了当下是要走更新流程,还是应该排队期待;如果轮到执行,就调用batchedUpdates
办法来间接发动更新流程。由此能够揣测,batchingStrategy
或者正是 React 外部专门用于管控批量更新的对象。
function enqueueUpdate(component) {ensureInjected();
// 留神这一句是问题的要害,isBatchingUpdates 标识着以后是否处于批量创立 / 更新组件的阶段
if (!batchingStrategy.isBatchingUpdates) {
// 若以后没有处于批量创立 / 更新组件的阶段,则立刻更新组件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”dirtyComponents.push(component);
if (component._updateBatchNumber == null) {component._updateBatchNumber = updateBatchNumber + 1;}
}
留神:batchingStrategy
对象能够了解为“锁管理器”。这里的“锁”,是指 React 全局惟一的 isBatchingUpdates
变量,isBatchingUpdates
的初始值是 false
,意味着“以后并未进行任何批量更新操作”。每当 React 调用 batchedUpdate
去执行更新动作时,会先把这个锁给“锁上”(置为 true
),表明“当初正处于批量更新过程中”。当锁被“锁上”的时候,任何须要更新的组件都只能临时进入 dirtyComponents
里排队等待下一次的批量更新,而不能随便“插队”。此处体现的“工作锁”的思维,是 React 面对大量状态依然可能实现有序分批解决的基石。
React 性能优化在哪个生命周期?它优化的原理是什么?
react 的父级组件的 render 函数从新渲染会引起子组件的 render 办法的从新渲染。然而,有的时候子组件的承受父组件的数据没有变动。子组件 render 的执行会影响性能,这时就能够应用 shouldComponentUpdate 来解决这个问题。
应用办法如下:
shouldComponentUpdate(nexrProps) {if (this.props.num === nexrProps.num) {return false}
return true;
}
shouldComponentUpdate 提供了两个参数 nextProps 和 nextState,示意下一次 props 和一次 state 的值,当函数返回 false 时候,render()办法不执行,组件也就不会渲染,返回 true 时,组件照常重渲染。此办法就是拿以后 props 中值和下一次 props 中的值进行比照,数据相等时,返回 false,反之返回 true。
须要留神,在进行新旧比照的时候,是 浅比照,也就是说如果比拟的数据时援用数据类型,只有数据的援用的地址没变,即便内容变了,也会被断定为 true。
面对这个问题,能够应用如下办法进行解决:
(1)应用 setState 扭转数据之前,先采纳 ES6 中 assgin 进行拷贝,然而 assgin 只深拷贝的数据的第一层,所以说不是最完满的解决办法:
const o2 = Object.assign({},this.state.obj)
o2.student.count = '00000';
this.setState({obj: o2,})
(2)应用 JSON.parse(JSON.stringfy())进行深拷贝,然而遇到数据为 undefined 和函数时就会错。
const o2 = JSON.parse(JSON.stringify(this.state.obj))
o2.student.count = '00000';
this.setState({obj: o2,})
在 React 中组件的 props 扭转时更新组件的有哪些办法?
在一个组件传入的 props 更新时从新渲染该组件罕用的办法是在 componentWillReceiveProps
中将新的 props 更新到组件的 state 中(这种 state 被成为派生状态(Derived State)),从而实现从新渲染。React 16.3 中还引入了一个新的钩子函数 getDerivedStateFromProps
来专门实现这一需要。
(1)componentWillReceiveProps(已废除)
在 react 的 componentWillReceiveProps(nextProps)生命周期中,能够在子组件的 render 函数执行前,通过 this.props 获取旧的属性,通过 nextProps 获取新的 props,比照两次 props 是否雷同,从而更新子组件本人的 state。
这样的益处是,能够将数据申请放在这里进行执行,须要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不用将所有的申请都放在父组件中。于是该申请只会在该组件渲染时才会收回,从而加重申请累赘。
(2)getDerivedStateFromProps(16.3 引入)
这个生命周期函数是为了代替 componentWillReceiveProps
存在的,所以在须要应用 componentWillReceiveProps
时,就能够思考应用 getDerivedStateFromProps
来进行代替。
两者的参数是不雷同的,而 getDerivedStateFromProps
是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。
须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
// 当传入的 type 发生变化的时候,更新 state
if (type !== prevState.type) {
return {type,};
}
// 否则,对于 state 不进行任何操作
return null;
}
React 的事件和一般的 HTML 事件有什么不同?
区别:
- 对于事件名称命名形式,原生事件为全小写,react 事件采纳小驼峰;
- 对于事件函数解决语法,原生事件为字符串,react 事件为函数;
- react 事件不能采纳 return false 的形式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()
来阻止默认行为。
合成事件是 react 模仿原生 DOM 事件所有能力的一个事件对象,其长处如下:
- 兼容所有浏览器,更好的跨平台;
- 将事件对立寄存在一个数组,防止频繁的新增与删除(垃圾回收)。
- 不便 react 对立治理和事务机制。
事件的执行程序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为须要冒泡到 document 上合成事件才会执行。
对 React 中 Fragment 的了解,它的应用场景是什么?
在 React 中,组件返回的元素只能有一个根元素。为了不增加多余的 DOM 节点,咱们能够应用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React 官网对 Fragment 的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 容许你将子列表分组,而无需向 DOM 增加额定节点。
import React, {Component, Fragment} from 'react'
// 个别模式
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// 也能够写成以下模式
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
React 事件机制
<div onClick={this.handleClick.bind(this)}> 点我 </div>
React 并不是将 click 事件绑定到了 div 的实在 DOM 上,而是在 document 处监听了所有的事件,当事件产生并且冒泡到 document 处的时候,React 将事件内容封装并交由真正的处理函数运行。这样的形式不仅仅缩小了内存的耗费,还能在组件挂在销毁时对立订阅和移除事件。
除此之外,冒泡到 document 上的事件也不是原生的浏览器事件,而是由 react 本人实现的合成事件(SyntheticEvent)。因而如果不想要是事件冒泡的话应该调用 event.preventDefault()办法,而不是调用 event.stopProppagation()办法。JSX 上写的事件并没有绑定在对应的实在 DOM 上,而是通过事件代理的形式,将所有的事件都对立绑定在了 document
上。这样的形式不仅缩小了内存耗费,还能在组件挂载销毁时对立订阅和移除事件。
另外冒泡到 document
上的事件也不是原生浏览器事件,而是 React 本人实现的合成事件(SyntheticEvent)。因而咱们如果不想要事件冒泡的话,调用 event.stopPropagation
是有效的,而应该调用 event.preventDefault
。
实现合成事件的目标如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创立一个事件对象。如果你有很多的事件监听,那么就须要调配很多的事件对象,造成高额的内存调配问题。然而对于合成事件来说,有一个事件池专门来治理它们的创立和销毁,当事件须要被应用时,就会从池子中复用对象,事件回调完结后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
React 的生命周期有哪些?
React 通常将组件生命周期分为三个阶段:
- 装载阶段(Mount),组件第一次在 DOM 树中被渲染的过程;
- 更新过程(Update),组件状态发生变化,从新更新渲染的过程;
- 卸载过程(Unmount),组件从 DOM 树中被移除的过程;
1)组件挂载阶段
挂载阶段组件被创立,而后组件实例插入到 DOM 中,实现组件的第一次渲染,该过程只会产生一次,在此阶段会顺次调用以下这些办法:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
(1)constructor
组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,然而若显式定义了构造函数,咱们必须在构造函数中执行 super(props)
,否则无奈在构造函数中拿到 this。
如果不初始化 state 或不进行办法绑定,则不须要为 React 组件实现构造函数Constructor。
constructor 中通常只做两件事:
- 初始化组件的 state
- 给事件处理办法绑定 this
constructor(props) {super(props);
// 不要在构造函数中调用 setState,能够间接给 state 设置初始值
this.state = {counter: 0}
this.handleClick = this.handleClick.bind(this)
}
(2)getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
这是个静态方法,所以不能在这个函数里应用 this
,有两个参数 props
和 state
,别离指接管到的新参数和以后组件的 state
对象,这个函数会返回一个对象用来更新以后的 state
对象,如果不须要更新能够返回 null
。
该函数会在装载时,接管到新的 props
或者调用了 setState
和 forceUpdate
时被调用。如当接管到新的属性想批改 state
,就能够应用。
// 当 props.counter 变动时,赋值给 state
class App extends React.Component {constructor(props) {super(props)
this.state = {counter: 0}
}
static getDerivedStateFromProps(props, state) {if (props.counter !== state.counter) {
return {counter: props.counter}
}
return null
}
handleClick = () => {
this.setState({counter: this.state.counter + 1})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
当初能够显式传入 counter
,然而这里有个问题,如果想要通过点击实现 state.counter
的减少,但这时会发现值不会产生任何变动,始终放弃 props
传进来的值。这是因为在 React 16.4^ 的版本中 setState
和 forceUpdate
也会触发这个生命周期,所以当组件外部 state
变动后,就会从新走这个办法,同时会把 state
值赋值为 props
的值。因而须要多加一个字段来记录之前的 props
值,这样就会解决上述问题。具体如下:
// 这里只列出须要变动的中央
class App extends React.Component {constructor(props) {super(props)
this.state = {
// 减少一个 preCounter 来记录之前的 props 传来的值
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比拟
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({counter: this.state.counter + 1})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
(3)render
render 是 React 中最外围的办法,一个组件中必须要有这个办法,它会依据状态 state
和属性 props
渲染组件。这个函数只做一件事,就是返回须要渲染的内容,所以不要在这个函数内做其余业务逻辑,通常调用该办法会返回以下类型中一个:
- React 元素:这里包含原生的 DOM 以及 React 组件;
- 数组和 Fragment(片段):能够返回多个元素;
- Portals(插槽):能够将子元素渲染到不同的 DOM 子树种;
- 字符串和数字:被渲染成 DOM 中的 text 节点;
- 布尔值或 null:不渲染任何内容。
(4)componentDidMount()
componentDidMount()会在组件挂载后(插入 DOM 树中)立刻调。该阶段通常进行以下操作:
- 执行依赖于 DOM 的操作;
- 发送网络申请;(官网倡议)
- 增加订阅音讯(会在 componentWillUnmount 勾销订阅);
如果在 componentDidMount
中调用 setState
,就会触发一次额定的渲染,多调用了一次 render
函数,因为它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,然而我该当防止这样应用,这样会带来肯定的性能问题,尽量是在 constructor
中初始化 state
对象。
在组件装载之后,将计数数字变为 1:
class App extends React.Component {constructor(props) {super(props)
this.state = {counter: 0}
}
componentDidMount () {
this.setState({counter: 1})
}
render () {
return (
<div className="counter">
counter 值: {this.state.counter} </div>
)
}
}
2)组件更新阶段
当组件的 props
扭转了,或组件外部调用了 setState/forceUpdate
,会触发更新从新渲染,这个过程可能会产生屡次。这个阶段会顺次调用上面这些办法:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
(1)shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
在说这个生命周期函数之前,来看两个问题:
- setState 函数在任何状况下都会导致组件从新渲染吗?例如上面这种状况:
this.setState({number: this.state.number})
- 如果没有调用 setState,props 值也没有变动,是不是组件就不会从新渲染?
第一个问题答案是 会,第二个问题如果是父组件从新渲染时,不论传入的 props 有没有变动,都会引起子组件的从新渲染。
那么有没有什么办法解决在这两个场景下不让组件从新渲染进而晋升性能呢?这个时候 shouldComponentUpdate
退场了,这个生命周期函数是用来晋升速度的,它是在从新渲染组件开始前触发的,默认返回 true
,能够比拟 this.props
和 nextProps
,this.state
和 nextState
值是否变动,来确认返回 true 或者 false
。当返回 false
时,组件的更新过程进行,后续的 render
、componentDidUpdate
也不会被调用。
留神: 增加 shouldComponentUpdate
办法时,不倡议应用深度相等查看(如应用 JSON.stringify()
),因为深比拟效率很低,可能会比从新渲染组件效率还低。而且该办法保护比拟艰难,倡议应用该办法会产生显著的性能晋升时应用。
(2)getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
这个办法在 render
之后,componentDidUpdate
之前调用,有两个参数 prevProps
和 prevState
,示意更新之前的 props
和 state
,这个函数必须要和 componentDidUpdate
一起应用,并且要有一个返回值,默认是 null
,这个返回值作为第三个参数传给 componentDidUpdate
。
(3)componentDidUpdate
componentDidUpdate() 会在更新后会被立刻调用,首次渲染不会执行此办法。该阶段通常进行以下操作:
- 当组件更新后,对 DOM 进行操作;
- 如果你对更新前后的 props 进行了比拟,也能够抉择在此处进行网络申请;(例如,当 props 未发生变化时,则不会执行网络申请)。
componentDidUpdate(prevProps, prevState, snapshot){}
该办法有三个参数:
- prevProps: 更新前的 props
- prevState: 更新前的 state
- snapshot: getSnapshotBeforeUpdate()生命周期的返回值
3)组件卸载阶段
卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及销毁之前间接调用。在此办法中执行必要的清理操作:
- 革除 timer,勾销网络申请或革除
- 勾销在 componentDidMount() 中创立的订阅等;
这个生命周期在一个组件被卸载和销毁之前被调用,因而你不应该再这个办法中应用 setState
,因为组件一旦被卸载,就不会再装载,也就不会从新渲染。
4)错误处理阶段
componentDidCatch(error, info),此生命周期在后辈组件抛出谬误后被调用。它接管两个参数∶
- error:抛出的谬误。
- info:带有 componentStack key 的对象,其中蕴含无关组件引发谬误的栈信息
React 常见的生命周期如下:React 常见生命周期的过程大抵如下:
- 挂载阶段,首先执行 constructor 构造方法,来创立组件
- 创立实现之后,就会执行 render 办法,该办法会返回须要渲染的内容
- 随后,React 会将须要渲染的内容挂载到 DOM 树上
- 挂载实现之后就会执行 componentDidMount 生命周期函数
- 如果咱们给组件创立一个 props(用于组件通信)、调用 setState(更改 state 中的数据)、调用 forceUpdate(强制更新组件)时,都会从新调用 render 函数
- render 函数从新执行之后,就会从新进行 DOM 树的挂载
- 挂载实现之后就会执行 componentDidUpdate 生命周期函数
- 当移除组件时,就会执行 componentWillUnmount 生命周期函数
React 次要生命周期总结:
- getDefaultProps:这个函数会在组件创立之前被调用一次(有且仅有一次),它被用来初始化组件的 Props;
- getInitialState:用于初始化组件的 state 值;
- componentWillMount:在组件创立后、render 之前,会走到 componentWillMount 阶段。这个阶段我集体始终没用过、十分鸡肋。起初 React 官网曾经不举荐大家在 componentWillMount 里做任何事件、到当初 React16 间接废除了这个生命周期,足见其鸡肋水平了;
- render:这是所有生命周期中惟一一个你必须要实现的办法。一般来说须要返回一个 jsx 元素,这时 React 会依据 props 和 state 来把组件渲染到界面上;不过有时,你可能不想渲染任何货色,这种状况下让它返回 null 或者 false 即可;
- componentDidMount:会在组件挂载后(插入 DOM 树中后)立刻调用,标记着组件挂载实现。一些操作如果依赖获取到 DOM 节点信息,咱们就会放在这个阶段来做。此外,这还是 React 官网举荐的发动 ajax 申请的机会。该办法和 componentWillMount 一样,有且仅有一次调用。
React 高阶组件是什么,和一般组件有什么区别,实用什么场景
官网解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。
高阶组件(HOC)就是一个函数,且该函数承受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由 react 本身的组合性质必然产生的。咱们将它们称为纯组件,因为它们能够承受任何动静提供的子组件,但它们不会批改或复制其输出组件中的任何行为。
// hoc 的定义
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {constructor(props) {super(props);
this.state = {data: selectData(DataSource, props)
};
}
// 一些通用的逻辑解决
render() {
// ... 并应用新数据渲染被包装的组件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
// 应用
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
1)HOC 的优缺点
- 长处∶ 逻辑服用、不影响被包裹组件的外部逻辑。
- 毛病∶hoc 传递给被包裹组件的 props 容易和被包裹后的组件重名,进而被笼罩
2)实用场景
- 代码复用,逻辑形象
- 渲染劫持
- State 形象和更改
- Props 更改
3)具体利用例子
- 权限管制: 利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度:页面级别和 页面元素级别
// HOC.js
function withAdminAuth(WrappedComponent) {
return class extends React.Component {
state = {isAdmin: false,}
async UNSAFE_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...
}
UNSAFE_componentWillMount() {// fetching data}
render() {// render page with data}
}
export default withAdminAuth(PageA);
// pages/page-b.js
class PageB extends React.Component {constructor(props) {super(props);
// something here...
}
UNSAFE_componentWillMount() {// fetching data}
render() {// render page with data}
}
export default withAdminAuth(PageB);
- 组件渲染性能追踪: 借助父组件子组件生命周期规定捕捉子组件的生命周期,能够不便的对某个组件的渲染工夫进行记录∶
class Home extends React.Component {render() {return (<h1>Hello World.</h1>);
}
}
function withTiming(WrappedComponent) {
return class extends WrappedComponent {constructor(props) {super(props);
this.start = 0;
this.end = 0;
}
UNSAFE_componentWillMount() {super.componentWillMount && super.componentWillMount();
this.start = Date.now();}
componentDidMount() {super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`${WrappedComponent.name} 组件渲染工夫为 ${this.end - this.start} ms`);
}
render() {return super.render();
}
};
}
export default withTiming(Home);
留神:withTiming 是利用 反向继承 实现的一个高阶组件,性能是计算被包裹组件(这里是 Home 组件)的渲染工夫。
- 页面复用
const withFetching = fetching => WrappedComponent => {
return class extends React.Component {
state = {data: [],
}
async UNSAFE_componentWillMount() {const data = await fetching();
this.setState({data,});
}
render() {return <WrappedComponent data={this.state.data} {...this.props} />;
}
}
}
// pages/page-a.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);
React 16.X 中 props 扭转后在哪个生命周期中解决
在 getDerivedStateFromProps 中进行解决。
这个生命周期函数是为了代替 componentWillReceiveProps
存在的,所以在须要应用 componentWillReceiveProps
时,就能够思考应用 getDerivedStateFromProps
来进行代替。
两者的参数是不雷同的,而 getDerivedStateFromProps
是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。
须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
// 当传入的 type 发生变化的时候,更新 state
if (type !== prevState.type) {
return {type,};
}
// 否则,对于 state 不进行任何操作
return null;
}
React.Component 和 React.PureComponent 的区别
PureComponent 示意一个纯组件,能够用来优化 React 程序,缩小 render 函数执行的次数,从而进步组件的性能。
在 React 中,当 prop 或者 state 发生变化时,能够通过在 shouldComponentUpdate 生命周期函数中执行 return false 来阻止页面的更新,从而缩小不必要的 render 执行。React.PureComponent 会主动执行 shouldComponentUpdate。
不过,pureComponent 中的 shouldComponentUpdate() 进行的是 浅比拟,也就是说如果是援用数据类型的数据,只会比拟不是同一个地址,而不会比拟这个地址外面的数据是否统一。浅比拟会疏忽属性和或状态渐变状况,其实也就是数据援用指针没有变动,而数据产生扭转的时候 render 是不会执行的。如果须要从新渲染那么就须要从新开拓空间援用数据。PureComponent 个别会用在一些纯展现组件上。
应用 pureComponent 的 益处:当组件更新时,如果组件的 props 或者 state 都没有扭转,render 函数就不会触发。省去虚构 DOM 的生成和比照过程,达到晋升性能的目标。这是因为 react 主动做了一层浅比拟。
state 和 props 触发更新的生命周期别离有什么区别?
state 更新流程: 这个过程当中波及的函数:
- shouldComponentUpdate: 当组件的 state 或 props 产生扭转时,都会首先触发这个生命周期函数。它会接管两个参数:nextProps, nextState——它们别离代表传入的新 props 和新的 state 值。拿到这两个值之后,咱们就能够通过一些比照逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之持续;
留神:此办法仅作为 性能优化的形式 而存在。不要希图依附此办法来“阻止”渲染,因为这可能会产生 bug。应该 思考应用内置的 PureComponent 组件,而不是手动编写
shouldComponentUpdate()
- componentWillUpdate:当组件的 state 或 props 产生扭转时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废除的三个生命周期之一。过来,咱们可能心愿能在这个阶段去收集一些必要的信息(比方更新前的 DOM 信息等等),当初咱们齐全能够在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
- componentDidUpdate:componentDidUpdate() 会在 UI 更新后会被立刻调用。它接管 prevProps(上一次的 props 值)作为入参,也就是说在此处咱们依然能够进行 props 值比照(再次阐明 componentWillUpdate 的确鸡肋哈)。
props 更新流程: 绝对于 state 更新,props 更新后惟一的区别是减少了对 componentWillReceiveProps 的调用。对于 componentWillReceiveProps,须要晓得这些事件:
- componentWillReceiveProps:它在 Component 承受到新的 props 时被触发。componentWillReceiveProps 会接管一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废除掉的三个生命周期之一。在它被废除前,能够用它来比拟 this.props 和 nextProps 来从新 setState。在 React16 中,用一个相似的新生命周期 getDerivedStateFromProps 来代替它。
对有状态组件和无状态组件的了解及应用场景
(1)有状态组件
特点:
- 是类组件
- 有继承
- 能够应用 this
- 能够应用 react 的生命周期
- 应用较多,容易频繁触发生命周期钩子函数,影响性能
- 外部应用 state,保护本身状态的变动,有状态组件依据内部组件传入的 props 和本身的 state 进行渲染。
应用场景:
- 须要应用到状态的。
- 须要应用状态操作组件的(无状态组件的也能够实现新版本 react hooks 也可实现)
总结: 类组件能够保护本身的状态变量,即组件的 state,类组件还有不同的生命周期办法,能够让开发者可能在组件的不同阶段(挂载、更新、卸载),对组件做更多的管制。类组件则既能够充当无状态组件,也能够充当有状态组件。当一个类组件不须要治理本身状态时,也可称为无状态组件。
(2)无状态组件 特点:
- 不依赖本身的状态 state
- 能够是类组件或者函数组件。
- 能够完全避免应用 this 关键字。(因为应用的是箭头函数事件无需绑定)
- 有更高的性能。当不须要应用生命周期钩子时,应该首先应用无状态函数组件
- 组件外部不保护 state,只依据内部组件传入的 props 进行渲染的组件,当 props 扭转时,组件从新渲染。
应用场景:
- 组件不须要治理 state,纯展现
长处:
- 简化代码、专一于 render
- 组件不须要被实例化,无生命周期,晋升性能。输入(渲染)只取决于输出(属性),无副作用
- 视图和数据的解耦拆散
毛病:
- 无奈应用 ref
- 无生命周期办法
- 无法控制组件的重渲染,因为无奈应用 shouldComponentUpdate 办法,当组件承受到新的属性时则会重渲染
总结: 组件外部状态且与内部无关的组件,能够思考用状态组件,这样状态树就不会过于简单,易于了解和治理。当一个组件不须要治理本身状态时,也就是无状态组件,应该优先设计为函数组件。比方自定义的 <Button/>
、<Input />
等组件。
React-Router 4 的 Switch 有什么用?
Switch 通常被用来包裹 Route,用于渲染与门路匹配的第一个子 <Route>
或 <Redirect>
,它外面不能放其余元素。
如果不加 <Switch>
:
import {Route} from 'react-router-dom'
<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>
Route 组件的 path 属性用于匹配门路,因为须要匹配 /
到 Home
,匹配 /login
到 Login
,所以须要两个 Route,然而不能这么写。这样写的话,当 URL 的 path 为“/login”时,<Route path="/" />
和<Route path="/login" />
都会被匹配,因而页面会展现 Home 和 Login 两个组件。这时就须要借助 <Switch>
来做到只显示一个匹配组件:
import {Switch, Route} from 'react-router-dom'
<Switch>
<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>
</Switch>
此时,再拜访“/login”门路时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是准确匹配门路,常常与<Switch>
联结应用。只有当 URL 和该 <Route>
的 path 属性完全一致的状况下能力匹配上:
import {Switch, Route} from 'react-router-dom'
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/login" component={Login}></Route>
</Switch>
高阶组件的利用场景
权限管制
利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度: 页面级别 和 页面元素级别
// 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、组件具备繁多职责:封装业务组件或者根底组件,如果不能给这个组件起一个有意义的名字,证实这个组件承当的职责可能不够繁多,须要持续抽组件,直到它能够是一个独立的组件即可
为什么应用 jsx 的组件中没有看到应用 react 却须要引入 react?
实质上来说 JSX 是 React.createElement(component, props, ...children)
办法的语法糖。在 React 17 之前,如果应用了 JSX,其实就是在应用 React,babel
会把组件转换为 CreateElement
模式。在 React 17 之后,就不再须要引入,因为 babel
曾经能够帮咱们主动引入 react。