★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
★★★★ 新出来两个钩子函数?和砍掉的will系列有啥区别?
★★ React 组件中 props 和 state 有什么区别?
★★★★ redux本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?
★★★ React组件生命周期按装载,更新,销毁三个阶段分别都有哪些?
★★★★★ 调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?
★★★ 循环执行setState组件会一直重新渲染吗?为什么?
★★★ React类组件,函数组件,在类组件修改组件对象会使用。
★★★ useEffect 和 useLayoutEffect 的区别
★★ React 中 refs 干嘛用的?如何创建 refs?
★★★ 在构造函数调用 super
并将 props
作为参数传入的作用是啥?
★★★ React 组件生命周期有哪些不同阶段?React 的生命周期方法有哪些?
★★★ React 中的StrictMode(严格模式)是什么?
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count
}
})
★★ 在 React 中使用构造函数和 getInitialState 有什么区别?
★★★★ Hooks 会取代 render props
和高阶组件吗?
★★★★ 当调用setState
时,React render
是如何工作的?
★★★ 在js原生事件中 onclick 和 jsx 里 onclick 的区别
★★★★ shouldComponentUpdate的作用是什么?
★★★ React状态管理工具有哪些?redux actionCreator都有什么?
★★★ redux中使用setState不能立刻获取值,怎么办
★★★ 为什么不建议在 componentWillMount 做AJAX操作
React.js 面试真题
★★★★ 虚拟DOM的优劣如何?实现原理?
/*
虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
优点:
(1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;
(2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;
(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;
缺点:
(1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;
React组件的渲染过程:
(1)使用JSX编写React组件后所有的JSX代码会通过Babel转化为 React.createElement执行;
(2)createElement函数对 key和 ref等特殊的 props进行处理,并获取 defaultProps对默认 props进行赋值,并且对传入的子节点进行处理,最终构造成一个 ReactElement对象(所谓的虚拟 DOM)。
(3)ReactDOM.render将生成好的虚拟 DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。
虚拟DOM的组成——ReactElementelement对象结构:
(1)type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class)
(2)key:组件的唯一标识,用于Diff算法,下面会详细介绍
(3)ref:用于访问原生dom节点
(4)props:传入组件的props,chidren是props中的一个属性,它存储了当前组件的孩子节点,可以是数组(多个孩子节点)或对象(只有一个孩子节点)
(5)owner:当前正在构建的Component所属的Component
(6)self:(非生产环境)指定当前位于哪个组件实例
(7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)
*/
★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
/*
react的diff算法只需要O(n),这是因为react对树节点的比较做了一些前提假设,限定死了一些东西,不做过于复杂的计算操作,所以降低了复杂度。react和vue做了以下的假设,这样的话diff运算时只进行同层比较,每一个节点只遍历了一次。
(1)Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计;
(2)拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;
(3)对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
而传统的diff运算时间复杂度为O(n^3),这是因为传统的树节点要做非常完整的检查,首先需要节点之间需要两两比较,找到所有差异,这个对比过程时间复杂度为O(n^2),找到差异后还要计算出最小的转换方式,最终复杂度为O(n^3)
*/
★★★ 聊聊 Redux 和 Vuex 的设计思想
/*
Flux的核心思想就是数据和逻辑永远单向流动,由三大部分组成 dispatcher(负责分发事件), store(负责保存数据,同时响应事件并更新数据)和 view(负责订阅store中的数据,并使用这些数据渲染相应的页面),Redux和Vuex是flux思想的具体实现,都是用来做状态管理的工具,Redux主要在react中使用,Vuex主要在vue中使用。
Redux设计和使用的三大原则:
(1)单一的数据源:整个应用的 state被储存在唯一一个 store中;
(2)状态是只读的:Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState;action是一个对象,有type(操作类型)和payload(新值)属性;
(3)状态修改均由纯函数完成:在Redux中,通过纯函数reducer来确定状态的改变,因为reducer是纯函数,所以相同的输入,一定会得到相同的输出,同时也不支持异步;返回值是一个全新的state;
vuex由State + Muatations(commit) + Actions(dispatch) 组成:
(1)全局只有一个Store实例(单一数据源);
(2)Mutations必须是同步事务,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定先后顺序,A、B两个mutation,调用顺序可能是A -> B,但是最终改变 State的结果可能是B -> A;
(3)Actions负责处理异步事务,然后在异步回调中触发一个或多个mutations,也可以在业务代码中处理异步事务,然后在回调中同样操作;
(4)模块化通过module方式来处理,这个跟Redux-combineReducer类似,在应用中可以通过namespaceHelper来简化使用;
*/
★★★ React中不同组件之间如何做到数据交互?
★★★ React中refs的作用是什么?
// ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;
// 类组件
class Foo extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return
<div>
<input ref={ this.myRef } />
<button onClick = {()=>this.handle()}>聚焦</button>
</div>
}
handle() {
// 通过current属性访问到当前元素
this.myRef.current.focus()
}
}
// 函数组件
function Foo() {
const inputEl = useRef(null)
const handle = () => {
inputEl.current.focus()
}
return
<div>
<input type="text" ref={ inputEl }/>
<button onClick = {handle}>聚焦</button>
</div>
}
★★★★ 请列举react生命周期函数。
/*
第一阶段:装载阶段3
constructor()
render()
componentDidMount()
第二阶段:更新阶段2
[shouldComponentUpdate()]
render()
componentDidUpdate()
第三阶段:卸载阶段1
componentWillUnmount()
constructor生命周期:
(1)当react组件实例化时,是第一个运行的生命周期;
(2)在这个生命周期中,不能使用this.setState();
(3)在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接等);
(4)不能把props和state交叉赋值;
componentDidMount生命周期:
(1)相当于是vue中的mounted;
(2)它表示DOM结构在浏览器中渲染已完成;
(3)在这里可以使用任何的副作用;
shouldComponentUpdate(nextProps,nextState)生命周期:
(1)相当于一个开关,如果返回true则更新机制正常执行,如果为false则更新机制停止;
(2)在vue中是没有的;
(3)存在的意义:可以用于性能优化,但是不常用,最新的解决方案是使用PureComponent;
(4)理论上,这个生命周期的作用,用于精细地控制声明式变量的更新问题,如果变化的声明式变量参与了视图渲染则返回true,如果被变化的声明式变量没有直接或间接参与视图渲染,则返回false;
componentDidUpdate生命周期:
(1)相当于vue中的updated();
(2)它表示DOM结构渲染更新已完成,只发生在更新阶段;
(3)在这里,可以执行大多数的副作用,但是不建议;
(4)在这里,可以使用this.setState(),但是要有终止条件判断。
componentWillUnmount生命周期:
(1)一般在这里清除定时器、长连接等其他占用内存的构造器;
render生命周期:
(1)render是类组件中唯一必须有的生命周期,同时必须有return(return 返回的jsx默认只能是单一根节点,但是在fragment的语法支持下,可以返回多个兄弟节点);
(2)Fragment碎片写法: <React.Fragment></React.Fragment> 简写成<></>;
(3)return之前,可以做任意的业务逻辑,但是不能使用this.setState(),会造成死循环;
(4)render()在装载阶段和更新阶段都会运行;
(5)当render方法返回null的时候,不会影响生命周期函数的正常执行。
*/
★★★ 组件绑定和js原生绑定事件哪个先执行?
// 先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段
★★ fetch的延时操作
// fetch语法:fetch(resource, config).then( function(response) { ... } );resource为要获取的资源,config是配置对象,包含method请求方法,headers请求头信息等
// 定义一个延时函数,返回一个promise
const delayPromise = (timeout=5000) => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
reject(new Error("网络错误"))
}, timeout)
})
}
// 定义一个fetch网络请求,返回一个promise
const fetchPromise = (resource, config) => {
return new Promise((resolve, reject)=>{
fetch(resource, config).then(res=>{
resolve(res)
})
})
}
// promise的race静态方法接受多个promise对象组成的数组,该数组中哪个promise先执行完成,race方法就返回这个promise的执行结果
const fetchRequest = (resource, config, timeout) => {
Promise.race([
delayPromise(timeout),
fetchPromise(resource,config)
])
}
★★ A 组件嵌套 B 组件,生命周期执行顺序
/*
父组件创建阶段的生命周期钩子函数 constructor
父组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 constructor
子组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 componentDidMount
父组件创建阶段的生命周期钩子函数 componentDidMount
*/
★★★ diff 和 Key 之间的联系
/*
diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。diff运算的主要流程见下:
1、tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。
2、component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比,叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后,组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。
React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动;
3、element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做element diff。
对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是,是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。
当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织重新渲染大的子树。
*/
★★★ 虚拟 dom 和原生 dom
/*
(1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;
(2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;
(3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;
(4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗
*/
★★★★ 新出来两个钩子函数?和砍掉的will系列有啥区别?
// react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
// 新增了方法
getDerivedStateFromProps // 静态方法
getSnapshotBeforeUpdate
/*
在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。
所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。
*/
★★★ react中如何打包上传图片文件
★★★ 对单向数据流和双向数据绑定的理解,好处?
/*
react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;
angular中实现了双向数据绑定,代码编写方便,但是不利于维护
*/
★★ React 组件中 props 和 state 有什么区别?
/*
1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等
2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;
*/
★★ react中组件分为那俩种?
// 类组件和函数组件
★★ react中函数组件和普通组件的区别?
见上
★★★★ react中 setState 之后做了什么?
/*
如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法,渲染数据;
如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;
*/
★★★★ redux本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?
/*
当我们需要修改store中值的时候,我们是通过 dispatch(action)将要修改的值传到reducer中的,这个过程是同步的,如果我们要进行异步操作的时候,就需要用到中间件;中间件其实是提供了一个分类处理action的机会,在 middleware 中,我们可以检阅每一个流过的action,并挑选出特定类型的 action进行相应操作,以此来改变 action;
applyMiddleware 是个三级柯里化的函数。它将陆续的获得三个参数:第一个是 middlewares 数组,第二个是 Redux 原生的 createStore,最后一个是 reducer;然后applyMiddleware会将不同的中间件一层一层包裹到原生的 dispatch 之上;
redux-thunk 中间件的作用就是让我们可以异步执行redux,首先检查参数 action 的类型,如果是函数的话,就执行这个 action这个函数,并把 dispatch, getState, extraArgument 作为参数传递进去,否则就调用next让下一个中间件继续处理action。
*/
// redux-thunk部分源码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument)
}
return next(action)
}
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk
★★★★ 列举重新渲染 render 的情况
this.setState()
this.forceUpdate()
// 接受到新的props
// 通过状态管理,mobx、redux等
// 改变上下文
★★★ React 按需加载
// 1、使用React.lazy, 但是React.lazy技术还不支持服务端渲染
const OtherComponent = React.lazy(() => import('./OtherComponent'))
// 2、使用Loadable Components这个库
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
★★★ React 实现目录树(组件自身调用自身)
★★★ React组件生命周期按装载,更新,销毁三个阶段分别都有哪些?
见上
★★★★★ 调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?
/*
如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;
如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;
触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;
*/
★★★ 如果我进行三次setState会发生什么
// 看情况,如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。
★★★ 循环执行setState组件会一直重新渲染吗?为什么?
见上
★★★ 渲染一个react组件的过程
/*
1、babel编译
当我们对代码进行编译的时候,babel会将我们在组件中编写的jsx代码转化为React.createElement的表达式,createElement方法有三个参数,分别为type(元素类型)、attributes(元素所有属性)、children(元素所有子节点);
2、生成element
当render方法被触发以后,createElement方法会执行,返回一个element对象,这个对象描述了真实节点的信息,其实就是虚拟dom节点;
3、生成真实节点(初次渲染)
这时候我们会判断element的类型,如果是null、false则实例一个ReactDOMEmptyComponent对象; 是string、number类型的话则实例一个ReactDOMTextComponent对象; 如果element是对象的话,会进一步判断type元素类型,是原生dom元素,则实例化ReactDOMComponent; 如果是自定义组件,则实例化ReactCompositeComponentWrapper;
在这些类生成实例对象的时候,在其内部会调用 mountComponent方法,这个方法里面有一系列浏览器原生dom方法,可以将element渲染成真实的dom并插入到文档中;
4、生命周期
componentDidMount:会在组件挂载后(插入DOM树中) 立即调用。一般可以在这里请求数据;
componentDidUpdate:会在数据更新后立即调用,首次渲染不会执行此方法;可以在其中直接调用 setState,但必须用if语句进行判断,防止死循环;
conponentWillUnmount:会在组件卸载及销毁之前调用,在此方法中执行必要的清理操作,如清除timer;
static getDerivedStateFromProps(prps,state):这个生命周期函数代替了componentWillMount和componentWillUpdate生命周期;props和state发生改变则调用,在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null则不更新任何内容;
shouldComponentUpdate(nextProps,nextState):这个生命周期函数的返回值用来判断React组件是否因为当前 state 或 props 更改而重新渲染,默认返回值是true;这个方法在初始化渲染或使用forceUpdate()时不会调用;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化。
*/
★★★ React类组件,函数组件,在类组件修改组件对象会使用。
★★★★ 类组件怎么做性能优化?函数组件怎么做性能优化?
/*
类组件:
(1)使用shouldComponentUpdate:这个生命周期可以让我们决定当前状态或属性的改变是否重新渲染组件,默认返回ture,返回false时不会执行render,在初始化渲染或使用forceUpdate()时不会调用;如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果,因为引用类型指向同一个地址;
当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化;
(2)React.PureComponent:基本上和Component用法一致,不同之处在于 PureComponent不需要开发者自己设置shouldComponentUpdate,因为PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate;但是如果props和state对象包含复杂的数据结构,它可能会判断错误(表现为对象深层的数据已改变,视图却没有更新);
(4)使用Immutable:immutable是一种持久化数据,一旦被创建就不会被修改,修改immutable对象的时候返回新的immutable;也就是说在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变;为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树中的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点仍然共享;
(5)bind函数:在react中改变this的指向有三种方法,a)constructor中用bind绑定; b)使用时通过bind绑定; 3)使用箭头函数;选择第一种只在组件初始化的时候执行一次,第二种组件在每次render都要重新绑定,第三种在每次render时候都会生成新的箭头函数,所以选择第一种;
函数组件:
(1)useCallback:接收一个函数作为参数,接收第二个参数作为依赖列表,返回值为函数,有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算;可以使用useCallback把要传递给子组件的函数包裹起来,这样父组件刷新的时候,传递给子组件的函数指向不会发生改变,可以减少子组件的渲染次数;
const handleUseCallback=useCallback(handleClick,[])
<Child handleClick={handleUseCallback} />
(2)useMemo:useMemo的使用和useCallback差不多,只是useCallback返回的是一个函数,useMemo返回值可以是函数、对象等都可以;
两者都可使用:
(1)React.memo:React.memo 功能同React.PureComponent,但React.memo是高阶组件,既可以用在类组件中也可以用在函数组件中;memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反;
(2)使用key:在列表渲染时使用key,这样当组件发生增删改、排序等操作时,diff运算后可以根据key值直接调整DOM顺序,避免不必要的渲染而避免性能的浪费;
(3)不要滥用props:尽量只传需要的数据,避免多余的更新,尽量避免使用{…props};
*/
★★★ useEffect 和 useLayoutEffect 的区别
/*
2、useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:
(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;
(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;
useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。
*/
★★★ hooks 的使用有什么注意事项
/*
(1)只能在React函数式组件或自定义Hook中使用Hook。
(2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
*/
★★★ 纯函数有什么特点,副作用函数特点
/*
纯函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。
纯函数的优点:无状态,线程安全;纯函数相互调用组装起来的函数,还是纯函数;应用程序或者运行环境可以对纯函数的运算结果进行缓存,运算加快速度。
函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。比如调接口、修改全局变量、抛出一个异常或以一个错误终止、打印到终端或读取用户输入、读取或写入一个文件等,所以说副作用是编程中最关键的部分,因为我们需要跟用户、跟数据进行交互。
*/
★★ React 中 refs 干嘛用的?如何创建 refs?
见上
★★★ 在构造函数调用 `super` 并将 `props` 作为参数传入的作用是啥?
/*
ES6 中在调用 super()方法之前,子类构造函数无法使用this引用,在react的类组件中也是如此;将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
*/
★★★ 如何 React.createElement ?
见上
★★★ 讲讲什么是 JSX ?
/*
JSX全称为JavaScript XML,是react中的一种语法糖,可以让我们在js代码中脱离字符串直接编写html代码;本身不能被浏览器读取,必须使用@babel/preset-react和webpack等工具将其转换为传统的JS。
主要有以下特点:
(1)类XML语法容易接受,结构清晰;
(2)增强JS语义;
(3)抽象程度高,屏蔽DOM操作,跨平台;
(4)代码模块化;
*/
★★★ 为什么不直接更新 `state` 呢?
// 如果试图直接更新 state ,则不会重新渲染组件;需要使用setState()方法来更新 state这样组件才会重新渲染;
★★★ React 组件生命周期有哪些不同阶段?React 的生命周期方法有哪些?
见上
★★★ 这三个点(...)在 React 干嘛用的?
// ...是es6语法新出的规范,叫做展开运算符;在react中可以将对象或数组进行展开,让我们操作改变数据结构非常方便。
★★★ React 中的 `useState()` 是什么?
// useState是一个内置的React Hook,可以让我们在函数组件中像类组件一样使用state并且改变state的值。
★★★ React 中的StrictMode(严格模式)是什么?
/*
React的StrictMode是一种辅助组件,用<StrictMode />包装组件,可以帮助我们编写更好的react组件,不会渲染出任何可见的ui;仅在开发模式下运行,它们不会影响生产构建,可以做以下检查:
(1)验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告;
(2)验证是否使用的已经废弃的方法,如果有,会在控制台给出警告;
(3)通过识别潜在的风险预防一些副作用。
*/
★★★ 为什么类方法需要绑定到类实例?
// 在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:
★★★★ 什么是 prop drilling,如何避免?
/*
从一个外部组件一层层将prop传递到内部组件很不方便,这个问题就叫做 prop drilling;主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护,代码看起来也变得冗余,不优雅;
为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过 Consumer组件或 useContext Hook 使用上下文数据。
*/
★★ 描述 Flux 与 MVC?
/*
传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:
数据流不够清晰——跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
缺乏数据完整性——模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。
使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。
*/
★★★ 这段代码有什么问题吗?
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count
}
})
// 没有问题
★★★★ 什么是 React Context?
What
Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的功能
Why
某些全局属性,通过父子props传递太过繁琐,Context提供了一种组件之间共享此类值的方式,而不必显式的通过组件树逐层传递props
When
共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言等
Where
- Context应用场景在于很多不同层级的组件访问同样的数据,这样也使得组件的复用性变差。
- 如果你只是想避免层层传递一些属性,组件组合有时候是一个比Context更好的方案,也就是直接传递组件
- 所以一个技术方案的选定需要针对不同的场景具体分析,采取合适的方案
How
// ①创建
const ThemeContext = React.createContext('xxx')
// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层
return (
<ThemeContext.Provider value="yyy">
{children}
<ThemeContext.Provider>
)
// ③使用---消费者 需要使用共享数据的子类中
// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext
render() {
let value = this.context
/* 基于这个值进行渲染工作 */
}
// 方式三
return(
<ThemeContext.Consumer>
{ value => /* 基于 context 值进行渲染*/ }
</ThemeContext.Consumer>
)
More
动态Context---类似父子组件
// ①创建
const ThemeContext = React.createContext({
value: 'xxx',
changeFunc: () => {} //通过context传递这个函数,让consumers组件更新context
})
// ②注入
return (
<ThemeContext.Provider value="yyy">
<Child changeFunc={this.changeFunc}>
<ThemeContext.Provider>
)
// ③消费
return(
<ThemeContext.Consumer>
{ ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
</ThemeContext.Consumer>
)
消费多个Context、注意事项等参考React中文网
★★★★★ 什么是 React Fiber?
What
- Fiber是React16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量式渲染,让界面渲染更流畅
- 一种流程控制原语,也称为协程,可以类比es6中的generator函数;React渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
- 一个执行单元,每次执行完一个“执行单元”,React就会检查现在还剩多少时间,如果没有时间就将控制权让出去。
- 目标
- 把可中断的工作拆分成小任务
- 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果
- 在父子任务之间从容切换(yield back and forth),以支持React执行过程中的布局刷新
- 支持render()返回多个元素
- 更好地支持error boundary
- 特性
- 增量渲染(把渲染任务拆分成块,匀到多帧)
- 更新时能够暂停,终止,复用渲染任务
- 给不同类型的更新赋予优先级
- 并发方面新的基础能力
★★★ 如何在 React 的 Props 上应用验证?
使用PropTypes进行类型检查
PropTypes自React v15.5起,请使用这个库prop-types
What & Why & When
- 随着应用的不断增长,也是为了使程序设计更加严谨,我们通常需要对数据的类型(值)进行一些必要的验证
- 出于性能方面的考虑,propTypes仅在开发模式下进行检测,在程序运行时就能检测出错误,不能使用到用户交互提醒用户操作错误等
- 也可以使用Flow或者TypeScript做类型检查,后期建议用typescript进行替代更好
Where
- class组件
- 函数组件
- React.memo高阶组件 可自行扩展
- React.forwardRef组件 可自行扩展
How
我们在组件类下添加一个静态属性 propTypes (属性名不能更改),它的值也是一个对象,用来设置组件中props的验证规则,key 是要验证的属性名称,value 是验证规则。
// 类组件
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
// 类组件在这里做检测
Greeting.propTypes = {
// v15.4 and below
// name: React.PropTypes.string
name: PropTypes.string
};
// 函数组件
unction HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
// 函数组件在这里做检测
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
More
- 限制单个元素 PropTypes.element
★★ 在 React 中使用构造函数和 getInitialState 有什么区别?
// ES6
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* initial state */ };
}
}
// ES5
var MyComponent = React.createClass({
getInitialState() {
return { /* initial state */ };
},
});
- 本质上其实是等价的?
- 区别在于ES6和ES5本身,getInitialState 是搭配 React.createClass 使用的, constructor 是搭配 React.Component 使用的
- 在React组件的生命周期中 constructor 先于 getInitialState
★★★ 如何有条件地向 React 组件添加属性?
- 对于某些属性,React足够智能可以忽略该属性,比如值为boolean值属性的值
- 也可以写控制语句管理是否给组件添加属性
★★★★ Hooks 会取代 `render props` 和高阶组件吗?
- 可以取代,但没必要
- 在Hook的渐进策略中也有提到,没有计划从React中移除class,在新的代码中同时使用Hook和class,所以这些方案目前还是可以有勇武之地
What
- 为什么要把这3种技术拿过来对比?
都在处理同一个问题,逻辑复用
- 高阶组件HOC---不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)
是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的 简单技术?
React16.8新增的特性,是一些可以让你在函数组件里“钩入”React state及生命周期等特性的函数,
Why
虽然 HOC & Render Props 能处理逻辑复用的问题,但是却存在各自的问题。
HOC 存在的问题
- 写法破坏了原来组件的结构,DevTools中组件会形成“嵌套地狱”
- 不要在 render 方法中使用 HOC 每次调用render函数会创建一个新的高阶组件导致该组件及其子组件的状态丢失
- 需要修复静态方法,即拷贝原组件的静态方法到高级组件中
- 如需传递Ref则需要通过React.forwardRef创建组件
Render Props 存在的问题
- 同样的写法会破坏原来组件的结构,DevTools中组件会形成“嵌套地狱”
- 与React.PureComponent组件使用有冲突
Hook 目前最优雅的实现,React为共享状态逻辑提供最好的原生途径
- 没有破坏性改动,完全可选,100%向后兼容
- 解决复杂组件,中逻辑状态、副作用和各种生命周期函数中逻辑代码混在一起,难以拆分,甚至形成bug的问题
- 处理class组件中
When
- 在函数组件中意识到要向其添加一些state---useState
- 有副作用的行为时
Where
- 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用
- 只能在函数组件或者自定义Hook中调用Hook
How
★★★ 如何避免组件的重新渲染?
Answer
当porps/state改变时组件会执行render函数也就是重新渲染
- class组件中 使用shouldComponentUpdate钩子函数
- PureComponent默认有避免重新渲染的功能
- 函数组件使用高阶组件memo处理
★★★ 什么是纯函数?
Answer
一个不会更改入参,且多次调用下相同的入参始终返回相同的结果
★★★★ 当调用`setState`时,React `render` 是如何工作的?
Answer
调用setState()
- 检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,expirationtime等…)
- 根据优先级相关参数判断更新模式是sync(同步更新)或是batched(批量处理)
- 加入执行更新事件的队列,生成事件队列的链表结构
- 根据链表顺序执行更新
setState既是同步的,也是异步的。同步异步取决于setState运行时的上下文。且setState 只在合成事件和钩子函数中是“异步”的,在原生DOM事件和 setTimeout 中都是同步的
render如何工作
- React在props或state发生改变时,会调用React的render方法,创建一颗不同的树
- React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
- diff算法,将两颗树完全比较更新的算法从O(n^3^),优化成O(n);
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 设置key来指定节点在不同的渲染下保持稳定
★★★ 如何避免在React重新绑定实例?
Answer
- 将事件处理程序定义为内联箭头函数
- 使用箭头函数来定义方法
- 使用带有 Hooks 的函数组件
★★★ 在js原生事件中 onclick 和 jsx 里 onclick 的区别
Answer
js原生中
- onclick添加事件处理函数是在全局环境下执行,污染了全局环境,
- 且给很多dom元素添加onclick事件,影响网页的性能,
- 同时如果动态的从dom树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露
jsx里的onClick
- 挂载的函数都控制在组件范围内,不会污染全局空间
- jsx中不是直接使用onclick,而是采取了事件委托的方式,挂载最顶层DOM节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个onClick都挂载一个事件处理函数要高
- 加上React控制了组件的生命周期,在unmount的时候自然能够清楚相关的所有事件处理函数,内存泄露不再是一个问题
★★★★ diff复杂度原理及具体过程画图
Answer
- React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
- React 通过分层求异的策略,对 tree diff 进行算法优化;
- React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
- React 通过设置唯一 key的策略,对 element diff 进行算法优化;
- 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
- 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
★★★★ shouldComponentUpdate的作用是什么?
What
- 不常用的生命周期方法,能影响组件是否重新渲染
- 在更新阶段,当有new props 或者 调用了 setState()方法,在render方法执行前会执行到,默认返回值为true,如果返回false则不刷新组件
Why & When & Where
- 如果你知道在什么情况下组件不需要更新,你可以让其返回值为false跳过整个渲染过程
- 次方法仅作为 性能优化方式 而存在,不要企图靠此方法来阻止渲染,
- 大部分情况下,使用PureComponent代替手写shouldComponentUpdate,仅浅层对比
- 不建议在shoulComponentUpdate中进行深层或者使用JSON.stringify(),这样非常影响效率和性能
Answer
- 作为React组件中不常用的生命周期函数,能影响组件是否重渲染
- 建议做浅层次的比较,来优化性能,当然这里也可以用PureComponent组件代替
- 如果有较深层次的比较则可能会导致更严重的性能问题,因此在这种情况下不要靠手动管理组件的重新渲染来优化性能,要找其他方式
- 比如?
★★★ React组件间信息传递
Answer
- 1.(父组件)向(子组件)传递信息 : porps传值
- 2.(父组件)向更深层的(子组件) 进行传递信息 : context
- 3.(子组件)向(父组件)传递信息:callback
- 4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值): 利用共同父组件context通信、自定义事件
- 5.利用react-redux进行组件之间的状态信息共享 : 组件间状态信息共享:redux、flux、mobx等
★★★ React状态管理工具有哪些?redux actionCreator都有什么?
Answer
- 简单状态管理:组件内部state、基*于Context API封装、
- 复杂状态管理:redux(单项数据流)、mobx(响应式数据流)、RxJS(stream)、dva
- 创建各种action,包含同步、异步,然后在组件中通过dispatch调用
★★★★ 什么是高阶组件、受控组件及非受控组件?都有啥区别
Answer
定义
- 高阶组件HOC---不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。
高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)
在表单元素中,state是唯一数据源,渲染表单的React组件控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素叫做受控组件
表单数据由DOM节点来处理,而不是用state来管理数据,一般可以使用ref来从DOM节点中获取表单数据
区别
- 受控组件和非受控组件是表单中的组件,高阶组件相当于对某个组件注入一些属性方法
- 高阶组件是解决代码复用性问题产生的技术
- 受控组件必须要有一个value,结合onChange来控制这个value,取值为event.target.value/event.target.checked
- 非受控组件相当于操作DOM,一般有个defaultValue,通过onBlur触发响应方法
★★★ vuex 和 redux 的区别?
Answer
Redux
- Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
Redux
- 随着JS单页面应用日趋复杂,JS需要管理比任何时候都要多的state(服务器响应,缓存数据,本地生成尚未持久化到服务器的数据,UI状态等)
- 伴随的现象就是models和views相互影响,你难以弄清楚变化的源头
- 也就是变化和异步让我们的state变得一团糟
★★★ Redux遵循的三个原则是什么?
Answer
单一数据源
- 整个应用的state被存储在一棵object tree中,并且整个 object tree 只存在于唯一一个 store 中
State是只读的
- 唯一改变state的方法就是触发 action,action是一个描述已发生事件的普通对象
- 这样确保视图和网络请求不能直接修改state
使用纯函数来执行修改
- 为了描述action如何改变state tree,你需要编写reducers
★★★ React中的keys的作用是什么?
Answer
key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。
- key 的唯一性
在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同 key 的都是同一个组件。
- key 值不可读
虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给 react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等
★★★ redux中使用setState不能立刻获取值,怎么办
Answer
setState 只在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步
- ①addeventListener添加的事件或者dom事件中触发
- ②setState接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态。
this.setState((preState) => {
return {
xxx: preState.xxx + yyy
}
})
- ③async/await 异步调用处理
★★ 什么是JSX
Answer
当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了
- 是一个 JavaScript 的语法扩展
- 具有 JavaScript 的全部功能
- 可以生成 React “元素”
- JSX 也是一个表达式
★★★ React新老版生命周期函数
Answer
New Version
- 挂载:constructor --> getDerivedStateFromProps --> render --> componentDidMount
- 更新:
- New props、setState() --> getDerivedStateFromProps --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
- forceUpdate() --> getDerivedStateFromProps --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
- 卸载: componentWillUnmount
Old Version*
- 挂载:constructor --> getDerivedStateFromProps --> render --> ComponentDidMount
- 更新:
- New props --> getDerivedStateFromProps --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
- setState() --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
- forceUpdate() --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
- 卸载:componentWillUnmount
★★★★ vue react都怎么检测数据变化
Answer
- React
React默认是通过比较引用的方式(diff)进行的,不精确监听数据变化,如果不优化可能导致大量不必要的VDOM重新渲染
- 16之前 componentWillReveiveProps 监听 props 变化
- 16之后 getDerivedStateFromProps 监听 props
- Vue
- vue监听变量变化依靠 watch Object.defineProperty,Vue通过“getter/setter”以及一些函数的劫持,能精确知道数据变化
★★★ React中怎么让 setState 同步更新?
Answer
- setState 回调,setState,第二个参数是一个回调函数,可实现同步
- 引入 Promise 封装 setState,在调用时我们可以使用 Async/Await 语法来优化代码风格
setStateAsync(state) { return new Promise((resolve) => { this.setState(state, resolve) }); }
- 传入状态计算函数, setState 的第一个参数,
this.setState((prevState, props) => ({ count: prevState.count + 1 }));
- 在 setTimeout 函数中调用 setState
- more?
★★★★ 什么是 immutable?为什么要使用它?
Answer
immutable是一种持久化数据。一旦被创建就不会被修改。修改immutable对象的时候返回新的immutable。但是原数据不会改变。
在Rudux中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。 但当你使用immutable数据的时候:只会拷贝你改变的节点,从而达到了节省性能。 总结:immutable的不可变性让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便。
因为在react中,react的生命周期中的setState()之后的shouldComponentUpdate()阶段默认返回true,所以会造成本组件和子组件的多余的render,重新生成virtual dom,并进行virtual dom diff,所以解决办法是我们在本组件或者子组件中的shouldComponentUpdate()函数中比较,当不需要render时,不render。
当state中的值是对象时,我们必须使用深拷贝和深比较!
如果不进行深拷贝后再setState,会造成this.state和nextState指向同一个引用,所以shouldComponentUpdate()返回值一定是false,造成state值改了,而组件未渲染(这里不管shouldComponentUpdate中使用的是深比较还是浅比较)。所以必须深拷贝。
如果不在shouldComponentUpdate中进行深比较,会造成即使state中的对象值没有改变,因为是不同的对象,而在shouldComponentUpdate返回true,造成不必要的渲染。
所以只能是深拷贝和深比较。
★★★ 为什么不建议在 componentWillMount 做AJAX操作
Answer
- Fiber原因,React16之后,采用了Fiber架构,只有componentDidMount的生命周期函数确定会执行一次,其他像componentWillMount可能会执行多次
- render 阶段 可能会被React暂停,中止或重启