1. React中keys的作用?
Keys是React用户追踪哪些列表中的元素被修改、被添加或者被移除的辅助标识。
2. 调用setState之后发生了什么
React会将传入的参数对象与组件当前的状态合并,然后触发调和过程。经过调和过程,React会以相对高效的方式根据新的状态构建React元素树,并且重新渲染整个UI界面。在React得到元素树之后,React会自动计算出新的树和老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
3. React生命周期函数
- 初始化阶段
- getDefaultProps:获取实例的默认属性
- getInitialState:获取每个实例的初始化状态
- componentWillMount:组件即将被装载、渲染到页面上
- render:组件在这里生成虚拟的DOM节点
- componentDidMount:组件真正在被装载之后
- 运行中状态
- componentWillReceiveProps:组件将要接收到属性的时候调用
- shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)
- componentWillUpdate:组件即将更新不能修改属性和状态
- render:组件重新描绘
- componentDidUpdate:组件已经更新
- 销毁阶段
- componentWillUnmount:组件即将销毁
4. shouldComponentUpdate是做什么的
用来判断是否需要调用render方法重绘dom。dom的描绘非常消耗性能,可以在shouldComponentUpdate中写出更优化的dom diff算法,来提高性能。
5. 为什么虚拟dom会提高性能
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免没有必要的dom操作来提高性能。
6.React diff原理
- 把树形结构安装层级分解,只比较同级元素。
- 把列表结构的每个单位添加唯一的key属性,方便比较。
- React只会匹配相同class的component(这里面的class指的是组件的名字)
- 合并操作,调用component的setState方法的时候,React奖其标记为dirty到每一个事件循环结束,React检查所有标记dirty的component重新绘制。
- 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。
7. React中refs的作用是什么
Refs是React提供给我们的安全访问DOM元素或者某个组件实例句柄。(详情实例)
8. 展示组件(Presentational component)、容器组件(Container component)、类组件(Class component)和函数式组件(Functional component)之间有何不同
- 展示组件关心组件看起来是什么。展示专门通过props接受数据和回调,并且几乎不会有自身的状态,但当展示组件拥有自身的状态时,通常也只关心UI状态而不是数据的状态。
- 容器组件更关心组件是如何运作的。容器组件会为展示组件或者其他容器组件提供数据和行为(behavior),他们会调用Flux actions,并将其作为回调函数提供给展示组件。容器组件经常是有状态的,因为它们是(其他组件的)数据源。
- 类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问store并维持状态。
- 当组件仅是接收props,并将组件自身渲染到页面时,该组件就是一个
无状态组件(stateless component),可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件。
9. (组件的)状态(state)和属性(props)之间有何不同
- State是一种数据结构,用于组件挂载时所需数据的默认值。State可能会随着时间的推移而发生突变,但更多时候是作为用户事件行为的结果。
- Props(properties的简写)则是组件的配置。props由父组件传递给子组件,并且就子组件而言,props是不可变的(immutable)。组件不能改变自身的props,但是可以把子组件的props放在一起(统一管理)。Props也不仅仅是数据-回调函数也可以通过props传递。
10. 什么是受控组件(controlled component)
在HTML中,类似input、textarea和select这样的表单元素会维护自身的状态,并基于用户的输入来更新。当用户提交表单时,前面提到的元素将随表单一起被发送。但在React中会有些不同,包含表单元素的组件将会在state中追踪输入的值,并且每次回调函数时,如onChange会更新state,更新渲染组件。一个输入表单元素,它的值通过React的这种方式来控制,这样的元素就被称为“受控元素”。
11. 什么是高阶组件(higher order component)
高阶组件时一个以组件为参数并返回一个新组件的函数。HOC运行你重用代码、逻辑和引导抽象。最常见的可能是Redux的connect函数。除了简单分享工具库和简单的组合,HOC最好的方式是共享React组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可复用的HOC。
11. 除了在构造函数中绑定this,你还有其他方式吗
你可以使用属性初始值设定项(property initializers)来正确绑定回调,create-react-app也是默认支持的。在回调中你可以使用箭头函数,但问题是每次组件渲染时都会创建一个新的回调。
12. 为什么建议传递给setState的参数是一个callback而不是一个对象
因为this.props和this.state的更新可能是异步的,不能依赖他们的值去计算下一个state。
13. 在构造函数中调用super(props)的目的是什么
在super()被调用之前,子类是不能使用this的,在ES2015中,子类必须在constructor中调用super()。传递props给super()的原因则是便于(在子类中)能在constructor访问this.props。
15. 在React组件的哪里发起Ajax请求,为什么
应该在componentDidMount中发起网络请求。这个方法会在组件第一次“挂载”(被添加到DOM)时执行,在组件的生命周期中仅会执行一次。更重要的是,你不能保证在组件挂载之前Ajax请求已经完成,如果是这样,也就意味着你讲尝试在一个未挂载的组件上调用setState,这将不起作用。在componentDidMount中发起网络请求将保证这有一个组件可以更新了。
16. 时间在React中的处理方式
为了解决跨浏览器兼容性问题,您的React中的事件处理程序将传递SyntheticEvent的实例,它是React的跨浏览器本机事件的跨浏览器包装器。 这些SyntheticEvent与您习惯的原生事件具有相同的接口,除了它们在所有浏览器中都兼容。有趣的是,React实际上并没有将事件附加到子节点本身。React将使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的,这也意味着在更新DOM时,React不需要担心跟踪事件监听器。
17. createElement和cloneElement有什么区别
React.createElement():JSX语法就是用React.createElement()来构建React元素。它接受三个参数,第一个参数可以是一个标签名。如div、span,或者React组件。第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。 React.cloneElement()与React.createElement()相似,不同的是它传入的第一个参数是一个React元素,而不是标签或组件。新添加的属性会并入原有的属性,传入到返回的新元素中,而旧的子元素将被替换。
18. React中构建组件的方式
React.createClass()、ES6 class和无状态函数。
19. Flux思想
Flux的最大特点,就是数据的“单向流动”。 1.用户访问View 2.View发出用户的Action 3.Dispatcher收到Action,要求Store进行相应的更新 4.Store更新后,发出一个"change"事件 5.View收到"change"事件后,更新页面
20. React常用脚手架
create-react-app 和Yeoman等
21. redux是什么?有什么缺点?
- redux是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action、store、reducer,工作流程是view调用store的dispatch接收aciton传入store,reducer进行state操作,view通过store提供的getState获取最新的数据,flux也是用来进行数据操作的,有四个组成部分aciton,dispatch,view,store,工作流程是view发出一个action,派发器接收action,让store进行数据更新,更新完成以后store发出change,view接受change更新视图。Redux和Flux很像。主要区别在于Flux有多个可以改变应用状态的store,在Flux中dispatcher被用来传递数据到注册的回调事件,但是在redux中只能定义一个可更新状态的store,redux把store和Dispatcher合并,结构更加简单清晰
- 新增state对状态的管理更加明确,通过redux,流程更加规范了,减少手动编码量,提高了编码效率,通过缺点是当数据更新时有时候组件不需要,但是也要重新绘制,有些影响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们。
- 缺点:一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会影响效率,或者需要写复杂的shouldComponentUpdate进行判断。
22. 为什么选择框架而不是原生
-
- 组件化:其中以React的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于组合扩展。
-
- 天然分层:JQuery时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是MVC、MVP还是MVVM模式都能帮助我们进行分层,代码耦合更易于读写。
-
- 生态:现在主流前端框架都自带生态,不管是数据流管理框架还是UI库都有成熟的解决方案。
-
- 开发效率:现代前端框架都默认自动更新DOM,而非我们手动操作,解决了开发者的手动DOM成本,提高开发效率,从根本上解决了UI与状态同步问题。
23. 虚拟DOM的优劣
优点:
- 保证性能下限:虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限。
- 无需手动操作DOM:虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高开发效率。
- 跨平台:虚拟DOM本质上是JavaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等。 缺点:
- 无法进行极致优化:在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化,比如VSCode采用直接手动操作DOM的方式进行极端的性能优化。
24. 虚拟DOM实现原理
- 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
- 状态变更时,记录新树和旧树的差异
- 最后把差异更新到真正的dom中
25. React组件通信如何实现
React组件间通信方式:
- 父组件向子组件通讯:父组件可以向子组件通过传props的方式,向子组件进行通讯
- 子组件向父组件通讯:props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息作为参数传递到父组件的作用域中
- 兄弟组件通信:找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 跨层级通信:Context设计目的是为了共享哪些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再合适不过
- 发布订阅模式:发布者发布时间,订阅监听时间并作出反应,我们可以通过引入event模块进行通信
- 全局状态管理工具:借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心store,并根据不同的事件产生新的状态
26. React如何进行组件/逻辑复用
抛开已经被官方弃用的Mixin,组件抽象的技术目前有三种比较主流:
- 高阶组件:
- 属性代理
- 反向继承
- 渲染属性
- react-hooks
27. mixin、hoc、render props和react-hooks的优劣如何
Mixin的缺陷:
- 组件与Mixin之间存在隐式依赖(Mixin经常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)
- 多个Mixin之间可能产生冲突(比如定义了相同的state字段)
- Mixin倾向于增加更多的状态,这降低了应用的可预测性,导致复杂度剧增
- 隐式依赖导致依赖关系不透明,维护成本和理解成本迅速攀升:
- 难以快速理解组件行为,需要全盘了解所有依赖Mixin的扩展行为,及其之间的相互影响
- 组件自身的方法和state字段不敢轻易删改,因为难以确定有没有Mixin依赖它
- Mixin也难以维护,因为Mixin逻辑最后会被打平合并到一起,很难搞清楚一个Mixin的输入输出
HOC相比Mixin的优势:
- HOC通过外层组件通过Props影响内层组件的状态,而不是直接改变其State不存在冲突和互相干扰,这就降低了耦合度
- 不同于Mixin的打平+合并,HOC具有天然的层级结构(组件树结构),这又降低了复杂度
HOC的缺陷:
- 扩展性限制:HOC无法从外部访问子组件的State因此无法通过shouldComponentUpdate滤掉不必要的更新,React在支持ES6 Class之后提供了React.PureComponent来解决这个问题
- Ref传递问题:Ref被隔断,后来的React.forwardRef来解决这个问题
- Wrapper Hell:HOC可能出现多层包裹组件的情况,多层抽象同样增加了复杂度和理解成本
- 命名冲突:如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,如何覆盖老属性
- 不可见性:HOC相当于在原有组件外层再包装一个组件,你压根不知道外层的包装是啥,对于你是黑盒
Render Props优点:
- 上述HOC的缺点Render Props都可以解决
Render Props缺陷:
- 使用繁琐:HOC使用只需要借助装饰器语法通常一行代码就可以进行复用,Render Props无法做到如此简单
- 嵌套过深:Render Props虽然摆脱了组件多层嵌套的问题,但是转化为了函数回调的嵌套
React Hook优点:
- 简洁:React Hooks解决了HOC和Render Props的嵌套问题,更加简洁
- 解耦:React Hooks可以更方便地把UI和状态分离,做到更彻底的解耦
- 组合:Hooks中可以引用另外的Hooks形成新的Hooks,组合变化万千
- 函数友好:React Hooks为函数组件而生,从而解决了类组件的几大问题:
- this指向容易错误
- 分割在不同声明周期中的逻辑使得代码难以理解和维护
- 代码复用成本高(高阶组件容易使代码量剧增)
React Hooks缺陷:
- 额外的学习成本(Functional Component与Class Component之间的困惑)
- 写法上又限制(不能出现在条件、循环中),并且写法增加了重构成本
- 破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)
- 在闭包场景可能会引用到旧的state、props值
- 内部实现上不直观(依赖一份可变的全局状态,不再那么“纯”)
- React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)
28.你是如何理解fiber的
React Fiber是一种基于浏览器的单线程调度算法
React 16之前,reconcilation算法实际是递归,想要中孤单递归是很困难的,React 16开始使用了循环替代之前的递归
Fiber:一种将recocilation(递归diff),拆分成无数个小任务的算法;它随时能够停止,恢复。停止恢复的时机取决于当前的一帧(16ms)内,还有没有足够的时间允许计算。
29.对Time Slice的理解
时间分片
- React在渲染(render)的时候,不会堵塞现在的线程
- 如果你设备足够快,你会感觉渲染是同步的
- 如果你设备非常慢,你会感觉还算是灵敏的
- 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
- 同样书写组件的方式
时间分片正是基于可随时打断、重启的Fiber架构,可打断当前任务,优先处理紧急且重要的任务,保证页面的流畅运行
30.redux的工作流程
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store.
- State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State.
- Action:State的变化,会导致View的变化。但是用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。
- Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生产Action,这个函数就叫Action Creator。
- Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,ta,,,,