1.js原型链
1、所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
2、所有的引用类型都有一个’_ _ proto_ '属性(也叫隐式原型,它是一个普通的对象)。
3、所有的函数都有一个’prototype’属性(这也叫显式原型,它也是一个普通的对象)。
4、所有引用类型,它的’ _ proto_ '属性指向它的构造函数的’prototype’属性。
5、当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的’ _ proto_ _'属性(也就是它的构造函数的’prototype’属性)中去寻找。
2.React 事件绑定原理
一、react并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件,采用小驼峰命名法,默认的事件传播方式是冒泡,如果想改为捕获的话,直接在事件名后面加上Capture即可;事件对象event也不是原生事件对象,而是合成对象,但通过nativeEvent属性可以访问原生事件对象;
二、react合成事件主要分为以下三个过程:
1、事件注册
在该阶段主要做了两件事:document上注册、存储事件回调。所有事件都会注册到document上,拥有统一的回调函数dispatchEvent来执行事件分发,类似于document.addEventListener(“click”,dispatchEvent)。
register:
addEventListener-click
addEventListener-change
listenerBank:
{
click: {key1: fn1, key2: fn2},
change: {key1: fn3, key3: fn4}
}
2、事件合成
事件触发后,会执行一下过程:
(1)进入统一的事件分发函数dispatchEvent;
(2)找到触发事件的 ReactDOMComponent;
(3)开始事件的合成;
—— 根据当前事件类型生成指定的合成对象
—— 封装原生事件和冒泡机制
—— 查找当前元素以及他所有父级
—— 在listenerBank根据key值查找事件回调并合成到 event(合成事件结束)
3、批处理
批量处理合成事件内的回调函数
3.React中的 setState 缺点是什么呢
setState执行的时候可以简单的认为,隶属于原生js执行的空间,那么就是属于同步,被react处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用setState修改值,那么在异步中会先进行合并,再进行渲染,降低了操作dom的次数,具体如下:
(1)setState 在合成事件和钩子函数中是“异步”的,在原生事件和 `setTimeout` 中都是同步的。
(2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
(3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
(4)正是由于setState存在异步的机制,如果setState修改值的时候依赖于state本身的值,有时候并不可靠,这时候我们需要传入一个回调函数作为其入参,这个回调函数的第一个参数为更新前的state值。
4.React组件通信如何实现
react本身:
(1)props——父组件向子组件通过props传参
(2)实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了
(3)回调函数——用于子组件向父组件通信,子组件调用props传递过来的方法
(4)状态提升——两个子组件可以通过父组件定义的参数进行传参
(5)Context上下文——一般用作全局主题
(6)Render Props——渲染的细节由父组件控制
状态管理:
(1) mobx/redux/dva——通过在view中触发action,改变state,进而改变其他组件的view
5.类组件和函数组件的区别
(1)语法上:函数组件是一个函数,返回一个jsx元素,而类组件是用es6语法糖class定义,继承component这个类
(2)类组件中可以通过state进行状态管理,而在函数组件中不能使用setState(),在react16.8以后,函数组件可以通过hooks中的useState来模拟类组件中的状态管理;
(3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助hooks来使用生命周期函数;
(4)类组件能够捕获最新的值(永远保持一致),这是因为当实例的props属性发生修改时,class组件能够直接通过this捕获到组件最新的props;而函数式组件是捕获渲染所使用的值,已经因为javascript闭包的特性,之前的props参数保存在内存之中,无法从外部进行修改。
6.请你说说React的路由是什么?
路由分为前端路由和后端路由,后端路由是服务器根据用户发起的请求而返回不同内容,前端路由是客户端根据不同的URL去切换组件;在web应用前端开发中,路由系统是最核心的部分,当页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。
react生态中路由通常是使用react-router来进行配置,其主要构成为:
(1)Router——对应路由的两种模式,包括与;
(2)route matching组件——控制路径对应的显示组件,可以进行同步加载和异步加载,;
(3)navigation组件——用做路由切换和跳转,;
BrowserRouter与HashRouter的区别:
(1)底层原理不一样:BrowserRouter使用的是H5的history API,不兼容IE9及以下版本;HashRouter使用的是URL的哈希值;
(2)path表现形式不一样:BrowserRouter的路径中没有#,例如:localhost:3000/demo/test;HashRouter的路径包含#,例如:localhost:3000/#/demo/test;
(3)刷新后对路由state参数的影响:BrowserRouter没有任何影响,因为state保存在history对象中;HashRouter刷新后会导致路由state参数的丢失;
7.React有哪些性能优化的手段?
1、使用纯组件;
2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;
3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
4、路由懒加载;
5、使用 React Fragments 避免额外标记;
6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
9、避免使用内联样式属性;
10、优化 React 中的条件渲染;
11、不要在 render 方法中导出数据;
12、列表渲染的时候加key;
13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
14、类组件中使用immutable对象;
8.React hooks 用过吗,为什么要用?
Hooks 是React在16.8版本中出的一个新功能,本质是一种函数,可以实现组件逻辑复用,让我们在函数式组件中使用类组件中的状态、生命周期等功能,hooks的名字都是以use开头。
react:
1、useState——创建状态
接收一个参数作为初始值;返回一个数组,第一个值为状态,第二个值为改变状态的函数
2、useEffect——副作用(数据获取、dom操作影响页面——在渲染结束之后执行
(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的时候先执行返回函数再执行参数函数
(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数
3、useRef
返回一个可变的ref对象,此索引在整个生命周期中保持不变。可以用来获取元素或组件的实例,用来做输入框的聚焦或者动画的触发。
4、useMemo——优化函数组件中的功能函数——在渲染期间执行
(1)接收一个函数作为参数,同样接收第二个参数作为依赖列表,返回值可以是任何,函数、对象等都可以
(2)这种优化有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算
5、useContext——获取上下文注入的值
(1)接受一个context 对象,并返回该对象<MyContext.Provider> 元素的 value值;
const value = useContext(MyContext);
6、useLayoutEffect——有DOM操作的副作用——在DOM更新之后执行
和useEffet类似,但是执行时机不同,useLayoutEffect在DOM更新之后执行,useEffect在render渲染结束后执行,也就是说useLayoutEffect比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束
7、useCallback——与useMemo类似
useMemo与useCallback相同,接收一个函数作为参数,也同样接收第二个参数作为依赖列表;useCallback是对传过来的回调函数优化,返回的是一个函数
react-router:
被route包裹的组件,可以直接使用props进行路由相关操作,但是没有被route包裹的组件只能用withRouter高阶组件修饰或者使用hooks进行操作
1、useHistory——跳转路由
2、useLocation——得到url对象
3、useParams——得到url上的参数
react-redux:
1、useSelector——共享状态——从redux的store中提取数据
2、useDispatch——共享状态——返回redux的store中对dispatch的引用
9.虚拟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)
10.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)
11.JS运行机制(进程与线程的区分)
1.根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。
2.从属关系不同:进程中包含了线程,线程属于进程。
3.开销不同:进程的创建、销毁和切换的开销都远大于线程。
4.拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。
5.控制和影响能力不同:子进程无法影响父进程,而子线程可以影响父线程,如果主线程发生异常会影响其所在进程和子线程。
6.CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。
7.操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。
JS单线程带来的好处:
JS主要是面向浏览器的,因此是和用户实时交互的,如果多线程执行的话,你无法确定同时开始的任务哪个会先结束,以网页加载为例,可能导致网页HTML结构已经加载好,但是CSS样式还未加载完成,导致用户浏览体验差。或者两个线程同时对一个DOM结点进行修改和删除操作,则无法判断以哪个线程为准。
12.聊聊 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来简化使用;
13.React中refs的作用是什么?
// ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;
// 类组件
class Foo extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return
<input ref={ this.myRef } />
<button onClick = {()=>this.handle()}>聚焦
}
handle() {
// 通过current属性访问到当前元素
this.myRef.current.focus()
}
}
// 函数组件
function Foo() {
const inputEl = useRef(null)
const handle = () => {
inputEl.current.focus()
}
return
<input type=“text” ref={ inputEl }/>
聚焦
}
14.请列举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的时候,不会影响生命周期函数的正常执行。
15.组件绑定和js原生绑定事件哪个先执行?
先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段
16.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)
])
}
17.A 组件嵌套 B 组件,生命周期执行顺序
父组件创建阶段的生命周期钩子函数 constructor
父组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 constructor
子组件创建阶段的生命周期钩子函数 render
子组件创建阶段的生命周期钩子函数 componentDidMount
父组件创建阶段的生命周期钩子函数 componentDidMount
18.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这个生命周期来选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织重新渲染大的子树。
19.虚拟 dom 和原生 dom
(1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;
(2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;
(3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;
(4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗
20.新出来两个钩子函数?和砍掉的will系列有啥区别?
// react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
// 新增了方法
getDerivedStateFromProps // 静态方法 通过修改props,来修改state的作用
getSnapshotBeforeUpdate //在render之前调用,state已更新,获取render之前的dom状态
static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// 当传入的type发生变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进行任何操作
return null;
}
在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。
所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。
React Fiber是react执行渲染时的一种新的调度策略,JavaScript是单线程的,一旦组件开始更新,主线程就一直被React控制,这个时候如果再次执行交互操作,就会卡顿。
React Fiber重构这种方式,渲染过程采用切片的方式,每执行一会儿,就歇一会儿。如果有优先级更高的任务到来以后呢,就会先去执行,降低页面发生卡顿的可能性,使得React对动画等实时性要求较高的场景体验更好。
21.react中如何打包上传图片文件
<Upload
{...actionprops}
>
{ systemOne ? systemOne.imgUrl ? <img src={systemOne.imgUrl} alt="图片加载异常" style={{ width: '50%' }} /> : uploadButton: ''}
</Upload>
22.对单向数据流和双向数据绑定的理解,好处?
/*
react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;
angular中实现了双向数据绑定,代码编写方便,但是不利于维护
*/
23.React 组件中 props 和 state 有什么区别?
/*
1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等
2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;
*/
24.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
25.列举重新渲染 render 的情况
this.setState()
this.forceUpdate()
// 接受到新的props
// 通过状态管理,mobx、redux等
// 改变上下文
26.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'))
27.React 实现目录树(组件自身调用自身)
28.调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?
/*
如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;
如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;
触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;
*/
29.如果我进行三次setState会发生什么
看情况,如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。
30.循环执行setState组件会一直重新渲染吗?为什么?
不会,会进行合并,只修改一次state的值,调用一次render。
31.渲染一个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来优化。
32.类组件怎么做性能优化?函数组件怎么做性能优化?
类组件:
(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,[])
(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};
33.useEffect 和 useLayoutEffect 的区别
2、useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:
(1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;
(2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;
useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。