共计 11197 个字符,预计需要花费 28 分钟才能阅读完成。
setState 是同步的还是异步的
有时体现出同步,有时体现出异步
- setState 只有在 React 本身的合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步的
- setState 的异步并不是说外部由异步代码实现,其实自身执行的过程和代码都是同步的,只是合成事件和钩子函数中没法立马拿到更新后的值,造成了所谓的异步。当然能够通过 setState 的第二个参数中的 callback 拿到更新后的后果
- setState 的批量更新优化也是建设在异步(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在异步中如果对同一个值进行屡次 setState,setState 的批量更新策略会对其进行笼罩,去最初一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
- 合成事件中是异步
- 钩子函数中的是异步
- 原生事件中是同步
- setTimeout 中是同步
为什么虚构 dom 会进步性能
虚构 dom 相当于在 js 和实在 dom 两头加了一个缓存,利用 dom diff 算法防止了没有必要 的 dom 操作,从而进步性能
具体实现步骤如下:
- 用 JavaScript 对象构造示意 DOM 树的构造; 而后用这个树构建一个真正的 DOM 树,插到文档当中;
- 当状态变更的时候,从新结构一棵新的对象树。而后用新的树和旧的树进行比拟,记 录两棵树差别;
- 把 2 所记录的差别利用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
shouldComponentUpdate 有什么用?为什么它很重要?
组件状态数据或者属性数据产生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期办法 should ComponentUpdate 中,容许抉择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是依据新的状态,以最无效的形式更新用户界面。如果咱们晓得用户界面的某一部分不会扭转,那么没有理由让 React 弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate 办法中返回 false, React 将让以后组件及其所有子组件放弃与以后组件状态雷同。
如何用 React 构建(build)生产模式?
通常,应用 Webpack 的 DefinePlugin 办法将 NODE ENV 设置为 production。这将剥离 propType 验证和额定的正告。除此之外,还能够缩小代码,因为 React 应用 Uglify 的 dead-code 来打消开发代码和正文,这将大大减少包占用的空间。
参考:前端 react 面试题具体解答
HOC(高阶组件)
HOC(Higher Order Componennt) 是在 React 机制下社区造成的一种组件模式,在很多第三方开源库中体现弱小。
简述:
- 高阶组件不是组件,是 加强函数,能够输出一个元组件,返回出一个新的加强组件;
- 高阶组件的次要作用是 代码复用,操作 状态和参数;
用法:
- 属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 性能加强;
- 默认参数: 能够为组件包裹一层默认参数;
function proxyHoc(Comp) {
return class extends React.Component {render() {
const newProps = {
name: 'tayde',
age: 1,
}
return <Comp {...this.props} {...newProps} />
}
}
}
- 提取状态: 能够通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
function withOnChange(Comp) {
return class extends React.Component {constructor(props) {super(props)
this.state = {name: '',}
}
onChangeName = () => {
this.setState({name: 'dongdong',})
}
render() {
const newProps = {
value: this.state.name,
onChange: this.onChangeName,
}
return <Comp {...this.props} {...newProps} />
}
}
}
应用姿态如下,这样就能十分疾速的将一个 Input 组件转化成受控组件。
const NameInput = props => (<input name="name" {...props} />)
export default withOnChange(NameInput)
包裹组件: 能够为被包裹元素进行一层包装,
function withMask(Comp) {
return class extends React.Component {render() {
return (
<div>
<Comp {...this.props} />
<div style={{
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, .6)',
}}
</div>
)
}
}
}
反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,罕用于以下操作
function IIHoc(Comp) {
return class extends Comp {render() {return super.render();
}
};
}
渲染劫持 (Render Highjacking)
条件渲染: 依据条件,渲染不同的组件
function withLoading(Comp) {
return class extends Comp {render() {if(this.props.isLoading) {return <Loading />} else {return super.render()
}
}
};
}
能够间接批改被包裹组件渲染出的 React 元素树
操作状态 (Operate State) : 能够间接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易保护,审慎应用。
利用场景:
权限管制,通过形象逻辑,对立对页面进行权限判断,按不同的条件进行页面渲染:
function withAdminAuth(WrappedComponent) {
return class extends React.Component {constructor(props){super(props)
this.state = {isAdmin: false,}
}
async componentWillMount() {const currentRole = await getCurrentUserRole();
this.setState({isAdmin: currentRole === 'Admin',});
}
render() {if (this.state.isAdmin) {return <Comp {...this.props} />;
} else {return (<div> 您没有权限查看该页面,请分割管理员!</div>);
}
}
};
}
性能监控,包裹组件的生命周期,进行对立埋点:
function withTiming(Comp) {
return class extends Comp {constructor(props) {super(props);
this.start = Date.now();
this.end = 0;
}
componentDidMount() {super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`${WrappedComponent.name} 组件渲染工夫为 ${this.end - this.start} ms`);
}
render() {return super.render();
}
};
}
代码复用,能够将反复的逻辑进行形象。
应用留神:
- 纯函数: 加强函数应为纯函数,防止侵入批改元组件;
- 防止用法净化: 现实状态下,应透传元组件的无关参数与事件,尽量保障用法不变;
- 命名空间: 为 HOC 减少特异性的组件名称,这样能便于开发调试和查找问题;
- 援用传递 : 如果须要传递元组件的 refs 援用,能够应用 React.forwardRef;
-
静态方法 : 元组件上的静态方法并无奈被主动传出,会导致业务层无奈调用;解决:
- 函数导出
- 静态方法赋值
- 从新渲 染: 因为加强函数每次调用是返回一个新组件,因而如果在 Render 中应用加强函数,就会导致每次都从新渲染整个 HOC,而且之前的状态会失落;
React 怎么做数据的检查和变动
Model
扭转之后(可能是调用了setState
),触发了virtual dom
的更新,再用diff
算法来把 virtual DOM
比拟real DOM
,看看是哪个dom
节点更新了,再渲染real dom
介绍一下 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
ssr 原理是什么?
外围原理其实就是借助虚构 DOM 来实现 react 代码可能在服务器运行的,node 外面能够执行 react 代码
createElement 与 cloneElement 的区别是什么
createElement
函数是 JSX 编译之后应用的创立React Element
的函数,而cloneElement
则是用于复制某个元素并传入新的Props
react 的虚构 dom 是怎么实现的
首先说说为什么要应用
Virturl DOM
,因为操作实在DOM
的消耗的性能代价太高,所以react
外部应用js
实现了一套 dom 构造,在每次操作在和实在 dom 之前,应用实现好的 diff 算法,对虚构 dom 进行比拟,递归找出有变动的 dom 节点,而后对其进行更新操作。为了实现虚构DOM
,咱们须要把每一种节点类型形象成对象,每一种节点类型有本人的属性,也就是 prop,每次进行diff
的时候,react
会先比拟该节点类型,如果节点类型不一样,那么react
会间接删除该节点,而后间接创立新的节点插入到其中,如果节点类型一样,那么会比拟prop
是否有更新,如果有prop
不一样,那么react
会断定该节点有更新,那么重渲染该节点,而后在对其子节点进行比拟,一层一层往下,直到没有子节点
connect 原理
- 首先
connect
之所以会胜利,是因为Provider
组件: - 在原利用组件上包裹一层,使原来整个利用成为
Provider
的子组件 接管Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
connect
做了些什么。它真正连贯Redux
和React
,它包在咱们的容器组件的外一层,它接管下面Provider
提供的store
外面的state
和dispatch
,传给一个构造函数,返回一个对象,以属性模式传给咱们的容器组件
connect
是一个高阶函数,首先传入mapStateToProps
、mapDispatchToProps
,而后返回一个生产Component
的函数 (wrapWithConnect
),而后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个通过包裹的Connect
组件,
该组件具备如下特点
- 通过
props.store
获取先人Component
的store props
包含stateProps
、dispatchProps
、parentProps
, 合并在一起失去nextState
,作为props
传给真正的Component componentDidMount
时,增加事件this.store.subscribe(this.handleChange)
,实现页面交互 shouldComponentUpdate
时判断是否有防止进行渲染,晋升页面性能,并失去nextState
componentWillUnmount
时移除注册的事件this.handleChange
因为
connect
的源码过长,咱们只看次要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {constructor(props, context) {
// 从先人 Component 处取得 store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = {storeState: null}
// 对 stateProps、dispatchProps、parentProps 进行合并
this.updateState()}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据产生扭转时,Component 从新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 扭转 Component 的 state
this.store.subscribe(() = {
this.setState({storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件 Connect
return (<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {store: storeShape}
return Connect;
}
}
react 性能优化是哪个周期函数
shouldComponentUpdate
这个办法用来判断是否须要调用 render 办法从新描述 dom。因为 dom 的描述十分耗费性能,如果咱们能在shouldComponentUpdate 方
法中可能写出更优化的dom diff
算法,能够极大的进步性能
react 性能优化计划
- 重写
shouldComponentUpdate
来防止不必要的 dom 操作 - 应用
production
版本的react.js
- 应用
key
来帮忙React
辨认列表中所有子组件的最小变动
在生命周期中的哪一步你应该发动 AJAX 申请
咱们该当将 AJAX 申请放到
componentDidMount
函数中执行,次要起因有下
React
下一代和谐算法Fiber
会通过开始或进行渲染的形式优化利用性能,其会影响到componentWillMount
的触发次数。对于componentWillMount
这个生命周期函数的调用次数会变得不确定,React
可能会屡次频繁调用componentWillMount
。如果咱们将AJAX
申请放到componentWillMount
函数中,那么不言而喻其会被触发屡次,天然也就不是好的抉择。- 如果咱们将
AJAX
申请搁置在生命周期的其余函数中,咱们并不能保障申请仅在组件挂载结束后才会要求响应。如果咱们的数据申请在组件挂载之前就实现,并且调用了setState
函数将数据增加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount
函数中进行AJAX
申请则能无效防止这个问题
React setState 口试题,上面的代码输入什么
class Example extends React.Component {constructor() {super()
this.state = {val: 0}
}
componentDidMount() {this.setState({ val: this.state.val + 1})
console.log(this.state.val)
// 第 1 次 log
this.setState({val: this.state.val + 1})
console.log(this.state.val)
// 第 2 次 log
setTimeout(() => {this.setState({ val: this.state.val + 1})
console.log(this.state.val)
// 第 3 次 log
this.setState({val: this.state.val + 1})
console.log(this.state.val)
// 第 4 次 log
}, 0)
}
render() {return null}
}
// 答:0, 0, 1, 2
受控组件、非受控组件
- 受控组件就是扭转受控于数据的变动,数据变了页面也变了。受控组件更适合,数据驱动是 react 外围
- 非受控组件不是通过数据管制页面内容
setState
在理解 setState 之前,咱们先来简略理解下 React 一个包装构造: Transaction:
事务 (Transaction)
是 React 中的一个调用构造,用于包装一个办法,构造为: initialize – perform(method) – close。通过事务,能够对立治理一个办法的开始与完结;处于事务流中,示意过程正在执行一些操作
- setState: React 中用于批改状态,更新视图。它具备以下特点:
异步与同步: setState 并不是单纯的异步或同步,这其实与调用时的环境相干:
-
在 合成事件 和 生命周期钩子 (除 componentDidUpdate) 中,setState 是 ” 异步 ” 的;
-
起因: 因为在 setState 的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入 dirtyComponents 队列中期待执行;否则,开始执行 batchedUpdates 队列更新;
- 在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而 componentDidUpdate 是在更新之后,此时组件曾经不在事务流中了,因而则会同步执行;
- 在合成事件中,React 是基于 事务流实现的事件委托机制 实现,也是处于事务流中;
- 问题: 无奈在 setState 后马上从 this.state 上获取更新后的值。
- 解决: 如果须要马上同步去获取新值,setState 其实是能够传入第二个参数的。setState(updater, callback),在回调中即可获取最新值;
-
-
在 原生事件 和 setTimeout 中,setState 是同步的,能够马上获取更新后的值;
- 起因: 原生事件是浏览器自身的实现,与事务流无关,天然是同步;而 setTimeout 是搁置于定时器线程中延后执行,此时事务流已完结,因而也是同步;
- 批量更新 : 在 合成事件 和 生命周期钩子 中,setState 更新队列时,存储的是 合并状态(Object.assign)。因而后面设置的 key 值会被前面所笼罩,最终只会执行一次更新;
-
函数式 : 因为 Fiber 及 合并 的问题,官网举荐能够传入 函数 的模式。setState(fn),在 fn 中返回新的 state 对象即可,例如 this.setState((state, props) => newState);
- 应用函数式,能够用于防止 setState 的批量更新的逻辑,传入的函数将会被 顺序调用;
注意事项:
- setState 合并,在 合成事件 和 生命周期钩子 中屡次间断调用会被优化为一次;
-
当组件已被销毁,如果再次调用 setState,React 会报错正告,通常有两种解决办法
- 将数据挂载到内部,通过 props 传入,如放到 Redux 或 父级中;
- 在组件外部保护一个状态量 (isUnmounted),componentWillUnmount 中标记为 true,在 setState 前进行判断;
虚构 DOM 的引入与间接操作原生 DOM 相比,哪一个效率更高,为什么
虚构 DOM 绝对原生的 DOM 不肯定是效率更高,如果只批改一个按钮的文案,那么虚构 DOM 的操作无论如何都不可能比实在的 DOM 操作更快。在首次渲染大量 DOM 时,因为多了一层虚构 DOM 的计算,虚构 DOM 也会比 innerHTML 插入慢。它能保障性能上限,在实在 DOM 操作的时候进行针对性的优化时,还是更快的。所以要依据具体的场景进行探讨。
在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验 / 研发效率。虚构 DOM 不是别的,正是前端开发们为了谋求更好的研发体验和研发效率而发明进去的高阶产物。虚构 DOM 并不一定会带来更好的性能,React 官网也素来没有把虚构 DOM 作为性能层面的卖点对外输入过。** 虚构 DOM 的优越之处在于,它可能在提供更爽、更高效的研发模式(也就是函数式的 UI 编程形式)的同时,依然放弃一个还不错的性能。
hooks 罕用的
useEffct 应用:如果不传参数:相当于 render 之后就会执行
传参数为空数组:相当于 componentDidMount
如果传数组:相当于 componentDidUpdate
如果外面返回:相当于 componentWillUnmount
会在组件卸载的时候执行革除操作。effect 在每次渲染的时候都会执行。React 会在执行以后 effect 之前对上一个 effect 进行革除。useLayoutEffect:
useLayoutEffect 在浏览器渲染前执行
useEffect 在浏览器渲染之后执行
当父组件引入子组件以及在更新某一个值的状态的时候,往往会造成一些不必要的节约,而 useMemo 和 useCallback 的呈现就是为了缩小这种节约,进步组件的性能,不同点是:useMemo 返回的是一个缓存的值,即 memoized 值,而 useCallback 返回的是一个 memoized 回调函数。useCallback
父组件更新子组件会渲染, 针对办法不反复执行,包装函数返回函数;useMemo:
const memoizedValue =useMemo(callback,array)
callback 是一个函数用于解决逻辑
array 管制 useMemo 从新执⾏行的数组,array 扭转时才会 从新执行 useMemo
不传数组,每次更新都会从新计算
空数组,只会计算一次
依赖对应的值,当对应的值发生变化时,才会从新计算(能够依赖另外一个 useMemo 返回的值)
不能在 useMemo ⾥面写副作⽤逻辑解决,副作用的逻辑解决放在 useEffect 内进行解决
自定义 hook
自定义 Hook 是一个函数,其名称以“use”结尾,函数外部能够调用其余的 Hook,自定义 Hook 是一种天然遵循 Hook 设计的约定,而并不是 React 的个性
在我看来,自定义 hook 就是把一块业务逻辑独自拿出去写。const [counter, setCounter] = useState(0);
const counterRef = useRef(counter); // 能够保留上一次的变量
useRef 获取节点
function App() {const inputRef = useRef(null);
return <div>
<input type="text" ref={inputRef}/>
<button onClick={() => inputRef.current.focus()}>focus</button>
</div>
}
为何 React 事件要本人绑定 this
在 React 源码中,当具体到某一事件处理函数将要调用时,将调用 invokeGuardedCallback 办法。
function invokeGuardedCallback(name, func, a) {
try {func(a);
} catch (x) {if (caughtError === null) {caughtError = x;}
}
}
事件处理函数是间接调用的,并没有指定调用的组件,所以不进行手动绑定的状况下间接获取到的 this 是不精确的,所以咱们须要手动将以后组件绑定到 this 上