Appearance
React理解和特性
React 是一个用于构建用户界面的javascript库,通过组件化的方式解决视图层开发复用的问题,本质是一个组件化框架。
- 声明式 直观与组合
- 组件化 视图的拆分与模块复用,可以更容易做到高内聚低耩合
- 通用性 在于一次学习,随处编写
React diff算法运作原理
第一次render在执行的时候会将第一次虚拟dom做一次缓存,第二次渲染的时候后会将新的虚拟dom和老的虚拟dom进行对比。 这个对比的过程就是diff算法,在dom主要更新的时候通过diff算法可以计算出虚拟dom中真正变化的部分,从而只针对变化的部分进行部分更新渲染,避免造成性能浪费 为了优化diff算法,react中对普通的diff算法进行了三大策略: Tree diff:在对比时遇到同一类型的组件遵循tree diff,进行层级对比,对比时一旦遇到不同类型的组件,直接将这个不同的组件判断为脏组件,并且替换该组件和之下所有的子节点 Element diff:是针对同一层级的element节点的 Component diff:是组件间的对比 第一次render在执行的时候会将第一次的虚拟dom做一次缓存,第二次渲染的时候会将新的虚拟dom和老的虚拟dom进行对比。这个对比的过程其实就是diff算法。
在DOM需要更新的时候,通过diff算法可以 计算出 虚拟DOM 中真正变化的部分,从而只针对变化的部分进行更新渲染,避免”牵一发而动全身“,造成性能浪费。
为了优化diff算法,react中对普通的diff算法实行了三大策略
tree diff
对比时,遇到同一类型的组件遵循 tree diff,进行层级对比 对比时,一旦遇到不同类型的组件,直接将这个不同的组件判断为 dirty component(脏组件),并替换该组件和之下所有的子节点。 对比时,在同一类型的两个组件中,如果你知道这个组件的 Virtual DOM没有任何变化,你(开发者)就可以手动使用 shouldComponentUpdate() 来判断组件是否需要进行diff,进一步的提升了diff效率和性能 优化点: 避免使用结构相同但是类型不同的组件,因为虽然组件的结构不需要改动,但是由于类型不同的原因,diff会直接销毁该组件并重建,虽然这种情况极少出现,但是造成的性能浪费挺严重的。 对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化 component diff component diff是组件间的对比 在遇到组件之间的比较时,有三种策略
对比时,遇到同一类型的组件遵循 tree diff,进行层级对比 对比时,一旦遇到不同类型的组件,直接将这个不同的组件判断为 dirty component(脏组件),并替换该组件和之下所有的子节点。 对比时,在同一类型的两个组件中,如果你知道这个组件的 Virtual DOM没有任何变化,你(开发者)就可以手动使用 shouldComponentUpdate() 来判断组件是否需要进行diff,进一步的提升了diff效率和性能 优化点:
避免使用结构相同但是类型不同的组件,因为虽然组件的结构不需要改动,但是由于类型不同的原因,diff会直接销毁该组件并重建,虽然这种情况极少出现,但是造成的性能浪费挺严重的。 对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化
element diff element diff 是针对同一层级的element节点的 在双方同一层级的节点对比时,有三种情况 面对全新的节点时,执行插入操作 —— INSERT_MARKUP 这点不需要过多解释,面对多余的节点时,执行删除操作 —— REMOVE_NODE
删除操作有两种情况: 组件新集合中有组件旧集合中的类型,但对应的element不可更新,只能执行删除 旧组件不在新集合里面,执行删除,面对换位的节点时,执行移动操作 —— MOVE_EXISTING
比如该层级的组件原本是 [A,B,C,D] ,新的结构为 [A,D,B,C] ,只进行了移动操作。在传统的diff算法中,只要遇见不同(B/D)就删除并重新插入,这样的做法过于粗暴,浪费了很多可以复用的节点,所以在element diff中,对新旧该层级的对比双方都添加了唯一的key值进行区分,只要对应的key值对应的元素没有改变,则只需要执行移动即可。
细节: 新旧节点会遍历后对比下标,新的下标称为lastIndex,旧的称为index,如果lastIndex大于index,需要将节点旧的节点移动到新的位置,相反则不动。 如果没有找到对应位置节点,则执行新增; 如果旧的节点在新的节点组用不到,则执行删除;一般是在最后做删除操作。
特殊情形,最后一个节点移动到第一个位置,会导致,前面的n-1个节点都进行后移,影响性能。尽量避免这样的操作。
说说React 中jsx语法糖的本质?
Jsx是JavaScript的语法扩展,或者说是一个类似xml的ECMAScript的语法扩展,它本身没有太多的语法定义,也不期望引入更多的标准,在react中并不强制使用jsx,jsx通过类似xml的描述方式,描述函数对象,即使在使用了jsx也会在构建过程中通过bable转换成createElement所以jsx更像是react的语法糖
- 快速,JSX执行更快,因为它在编译为 JavaScript代码后进行了优化。
- 使用JS,通过React.createElement来创建 VDOM,当存在标签嵌套时,代码杂乱,不便于书写和检杳
- 二者的关系:JSX会由 BabelQ翻译成JS,因此浏览器是通过执行React.createElement来创建VDOM
- 安全,与JavaScript相比,JSX是静态类型的,大多是类型安全的。使用JSX进行开发时,应用程序的质量会变得更高,因为在编译过程中会发现许多错误,它也提供编译器级别的调试功能。
Babel实现jsx到js编译
Babel读取代码并解析,生成AST,再将AST 传 入插件层进行转换,在转换时就可以将 JSX 的结构转换为 React.createElement 的函数
React生命周期
挂载阶段:
constructor():在react组件挂载之前,会调用它的构造函数 ComponentWillMount():在render()方法之前调用,并且在初始化挂载及后续更新时都会被调用 ComponentDidMount():在挂载之后(插入dom树中)立即调用 更新阶段:
componentWillReceiveProps():在接收父组件改变后的props需要重新渲染组件时使用的比较多,外部组件频繁的时候会导致效率会比较低 ShouldComponentUpdate():用于控制组件重新渲染的流程,在这return false 可以阻止组件的更新 Render():render()方法是class组件当中唯一必须实现的方法 ComponentWillUpdate():ShouldComponentUpdate()返回true后,组件进入重新渲染之前进入这个函数 ComponentDidUpdate():每次改变state重新渲染页面后都会进入到这个生命周期 卸载销毁阶段
ComponentWillUnmount():在此完成组件的卸载和数据的销毁
React 生命周期坑
- 不在恰当的时候调用了不该调用的代码;
- 在需要调用时,不要忘了调用。
- ComponentWillMount在ssr中这个方法将会被重复触发很多遍同时在这里如果绑定事件,将如法解绑,会导致内存泄漏,变得不够安全高效逐步废弃
- ComponentWillReceiveProps外部组件多次频繁的更新传入多次不同的props,会导致不必要的异步请求
- ComponentWillUpdate更新前记录Dom状态,可能会做一些处理,与componentDidUpdate相隔时间如果过长,会导致状态不可信
- getDerivedStateFromProps函数 替换 componentWillMount和componentWillReceiveProps生命周期函数
- 用getSnapshotBeforeUpdate函数替换componentWillUpdate方法,避免和componentDidUpdate函数中获取数据不一致的问题
React Fiber原理和应用
Fiber框架的应用目的,按照react官方的说法就是实现‘增量渲染’,通俗来说就是把一个渲染任务分解成多个渲染任务,而后分散到多个帧里面
Fiber架构的核心:可断点、可恢复和优先级 解决了JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行的时候,另一个线程只能挂起等待,如果JavaScript线程长时间占用主线程那么渲染层面的更新不得不长时间的等待,界面长时间不更新就会导致页面响应变差,用户会感到卡顿,而fiber是在react渲染组件的时候,从开始到渲染完成整个过程是一气呵成无法中断的
React中虚拟dom的理解
虚拟dom不会进行排版和重绘的操作,我们使用虚拟dom来减少对真实dom的操作,可以达到使用虚拟dom提高性能,但是在首屏需要加载大量的dom时使用虚拟dom就会比不使用虚拟dom的速度要慢,因为虚拟dom会对真实dom进行一次对比
React jsx转换成真实DOM的过程?
使用react.createElement或者是JSX编写的react组件,实际上所有的jsx代码都会转换成react.createElement的内容,babel帮助我们完成了转换的过程,createElement函数对key和ref等特殊的props进行处理,并会获取defaultProps对默认props进行赋值,并且对传入的子节点进行处理,最终构造成一个虚拟dom对象
类组件和函数组件
- 组件区别
- class组件是有状态的组件,可以定义state状态,函数组件无状态
- class组件有生命周期的,函数组件无生命周期
- class组件是有this对象,函数组件没有this对象
- 组件调用:class组件实例化后调用render方法调用,函数组件直接调用的。
- class组件内部的话,render方法return返回 渲染jsx模板,函数组件直接返回即可
- ref获取子组件的对象,class组件可以直接获取到的,函数组件无法直接获取到。
- 綁定bind改变this指向,只适用于class组件
- 类组件this问题 给类组件实例化后的对象可以添加方法吗
- 函数组件为什么没有生命周期
- 纯函数和副作用
合成事件
合成事件就是不在浏览器本身触发的事件,自己创建和触发的事件
原理:首先会在fiber节点进入render阶段的component阶段的时候,将事件监听绑定在root上,然后调用ensureListeningTo事件绑定,生成事件合成对象,收集事件,触发真正的事件
React render方法的原理,在什么时候会触发?
Render函数里面可以编写JSX,转化成createElement这种实行,用于生成虚拟dom,最终转化成真实dom。
在react中,类组件只要执行了setState方法就一定会触发render函数执行,函数组件使用useState更改状态不一定会导致重新render组件的props;但是如果props的值来自于父组件的state,在这种情况下,父组件state发生了改变就会导致子组件的重新渲染,所以一旦指令setState就会执行render,useState会判断当前值有没有发生改变,确定是否去执行render方法,一旦父组件发生渲染,子组件也就会发生渲染
React 调和阶段setState干了什么?
> 代码中调用setState函数之后react就会将传入的参数对象与组件当前的状态合并,然后去触发所谓的调和过程,经过调和过程,react会以相对高效的方式根据新的状态进行构建react元素并且着手重新渲染整个ui界面
React组件之间如何通信?
- 父传子:在父组件中的子组件标签上绑定自定义属性,挂载传递的数据 子组件中props接收传递的数据,直接使用即可
- 子传父:父组件中的子组件标签上绑定一个属性,传递一个方法给子组件 子组件中通过props接收这个方法,直接调用,传递相应的参数即可
- 非父子组件:可以使用状态提升(中间人模式)、context状态树传参
- 状态提升(中间人模式):react中的状态提升概括来说就是将多个组件需要共享的状态提升到离它们最近的父组件,在父组件上改变这个状态,然后通过props分发给子组件
- Context状态树传参:在父组件中我们通过createContext()创建一个空对象,在父组件的最外层我们使用Provider包裹数据,通过value绑定要传递的对象数据 在嵌套的子组件中,我们有两种方式获取数据: 我们可以使用Customer标签,在标签中绑定一个箭头函数,函数的形参context就是value传递的数据 class组件中我们可以定义static contextType=context对象,组件中直接使用this.context获取数据
说说你对受控组件和非受控组件的理解?应用场景?
- 受控组件:由react控制的输入表单元素而改变其值的方式叫做受控组件
应用场景:比如表单元素input绑定一个onChange事件,当input状态发生变化时就会触发onChange事件,从而更新组件state
- 非受控组件:非受控组件就是指,表单数据由DOM本身处理,即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值,在非受控组件中,可以使用ref来从DOM获取表单值
应用场景:在输入框显示用户输入数据,并不更新state,在使用ref获取表单值是更新state
Connect组件的原理是什么?
connect有四个参数(两个不常用)
第一个:是mapStateToProps这个函数允许我们将store中的数据作为props绑定到数组上,主要原理就是将需要绑定的props作为一个函数传过来,在connect中传递给mapStateToProps一个真实的store数据
第二个:是mapDispatchToProps由于更改数据必须要触发action,因此在这个参数的主要功能就是将action作为props绑定到组件上
执行流程
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
useCall
- 使用
- 传递给子组件可以吗
React hook的理解?
可以使用hooks从组件中提取状态逻辑,让这些逻辑可以单独测试并且hooks使我们在无需修改组件结构的情况下复用状态逻辑,hooks将组件中互相关联的部分拆解成更小的函数,还可以使用reducer来管理组件内部状态让其更可预测,hooks让我们在非class组件的情况下使用更多的react的特性
props和state相同点和不同点?render方法在哪些情况下会执行?
- Props和state相同点 都是导出html的原始数据,都是确定性的,都是触发渲染更新,都是纯js对象
- 不同点
- props是指组件间传递的一种方式,由于react的数据流是自上而下的,所以是父组件向子组件进行传递,另外组件内部的this.props属性是只读不可以修改的 State是组件内部的状态,不能够直接修改,必须要通过setState来改变值的状态,从而达到更新组件内部数据的作用
- Render方法执行 在类组件抵用setState修改状态时;函数组件通过useState hook或者通过useState修改状态时;当我们的数据发生改变render方法就会触发
redux的实现原理是什么,写出核心代码?
Redux就是一个实现集中管理的容器,遵循三大原则,单一数据源,state是只读的,使用纯函数来进行修改,需要注意的是redux并不是只应用在react还与其他的界面库一起使用。
工作原理:redux要求我们把数据放在一个state公共存储的空间中,一个组件改变了state里面的数据,其他组件就能感受到state的变化,再来获取store里的数据,从而间接的实现了这些数据传递的功能
使用步骤:创建一个store文件夹,新建一个index.js文件,文件中导入redux的createStore方法,用于创建公共数据区域,创建一个reducer纯函数,接收两个参数state、action分别表示数据和操作state的方法,返回state数据给组件页面,把reducer作为createStore的参数抛出在需要使用的页面导入store文件,通过store.getState获取数据,通过store.dispatch触发action修改state数据,使用store.subscrible方法监听store的改变,避免数据不更新
说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
Redux中,中间件就是放在dispatch过程,在分发action进行拦截处理 当action发出之后,reducer立即算出state,整个过程是一个同步的操作。如果需要支持异步操作,这个过程就可以用上中间件,其本质上就是一个函数,对store.dispatch方法进行了改造,在发出action和执行reducer这两步间添加了其他的功能
常用的中间件:redux-thunk:用于异步操作、redux-logger:用于日志记录
中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,依次执行,然后作为第二个参数传入到createStore中
javascript
Const store = createStore(
Reducer,
applyMiddlewares(thunk,logger)
);
说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
@reduxjs/toolkit是redux官方强烈推荐的一个高效的redux的开发工具集,他的宗旨在称为标准的redux逻辑开发模式redux toolkit最初就是为了帮我们解决现有redux的常见的三个问题而创建的; 由于配置redux store 过于复杂,我们必须添加有关的redux的很多软件包,才可以使用redux
区别 react-redux 是的官方 React UI 绑定层,允许您的 React 组件从 Redux 存储中读取数据,并将操作分派到存储以更新状态。
@reduxjs/toolkit @reduxjs/toolkit 是对 Redux 的二次封装,开箱即用可的一个高效的 Redux 开发工具集,使得创建store、更新store
为什么react元素有一个$$type属性?
因为json不支持symbol类型,所以用户即使提交了message的信息,到最后服务器端也不会保存$$typeof的属性,而在渲染的时候,react会检测是否有 $ $typeof属性,如果没通信有这个属性则会拒绝处理该元素
props和state相同点和不同点?render方法在哪些情况下会执行?
- Props和state相同点 都是导出html的原始数据,都是确定性的,都是触发渲染更新,都是纯js对象
- 不同点
- props是指组件间传递的一种方式,由于react的数据流是自上而下的,所以是父组件向子组件进行传递,另外组件内部的this.props属性是只读不可以修改的 State是组件内部的状态,不能够直接修改,必须要通过setState来改变值的状态,从而达到更新组件内部数据的作用
- Render方法执行 在类组件抵用setState修改状态时;函数组件通过useState hook或者通过useState修改状态时;当我们的数据发生改变render方法就会触发
React性能优化的手段有哪些?
- 避免使用内联函数;
- 事件的绑定方式;
- 懒加载组件;
- 服务器端渲染;
- 数据的重复使用;
- 组件的复用;
- 使用React.Memo来缓存组件、
- 使用useMemo缓存大量的计算、
- 使用React.PureComponent , shouldComponentUpdate、
- 避免使用内联对象、
- 避免使用匿名函数、
- 延迟加载不是立即需要的组件、
- 调整CSS而不是强制组件加载和卸载、
- 使用React.Fragment避免添加额外的DOM