- React解决了什么问题
- 如何设计一个好的组件
- 组件的render函数在何时被调用
- 调用render时DOM就一定会被更新吗
- 组件的生命周期
- 进行远程数据加载时,应该在哪个周期中完成
- 在哪些生命周期中可以修改组件的state
- 不同父节点的组件需要对彼此的状态进行改变时应该怎么实现
- state里应该有什么
- 如何对组件进行优化
- 组件中的key属性有什么用
- Component与Element和Instance的区别
- 调用setState时,发生了什么事
- 什么时候使用类组件(Class Component)而非功能组件(Functional Component)
- 什么是React的refs,为什么它们很重要
- 受控组件(controlled component)和不受控组件(uncontrolled component)有什么区别
- 描述事件在React中的处理方式
React解决了什么问题?
a. React实现了Virtual DOM
在一定程度上提升了性能,尤其是在进行小量数据更新时。因为DOM操作是很耗性能的,而Virtual DOM是在内存中进行操作的,当数据发生变化时,通过diff算法比较两棵树之间的变化,再进行必要的DOM更新,省去了不必要的高消耗的DOM操作。当然,这种性能优化主要体现在有小量数据更新的情况下。因为React的基本思维模式是每次有变动就重新渲染整个应用,简单想来就是直接重置innerHTML,比如说在一个大型列表所有数据都变动的情况下,重置innerHTML还比较合理,但若是只有一行数据变了,它也需要重置整个innerHTML,就会造成大量的浪费。而Virtual DOM虽然进行了JS层面的计算,但是比起DOM操作来说,简直不要太便宜。
b. React的一个核心思想是声明式编程。
命令式编程是解决做什么的问题,就像是下命令一样,关注于怎么做,而声明式编程关注于得到什么结果,在React中,我们只需要关注“目前的状态是什么”,而不是“我需要做什么让页面变成目前的状态”。React就是不断声明,然后在特定的参数下渲染UI界面。这种编程方式可以让我们的代码更容易被理解,从而易于维护。
c. 组件化
React天生组件化,我们可以将一个大的应用分割成很多小组件,这样有好几个优势。首先组件化的代码像一棵树一样清楚干净,比起传统的面条式代码可读性更高;其次前端人员在开发过程中可以并行开发组件而不影响,大大提高了开发效率;最重要的是,组件化使得复用性大大提高,团队可以沉淀一些公共组件或工具库。
d. 单向数据流
在React中数据流是单向的,由父节点流向子节点,如果父节点的props发生了变化,那么React会递归遍历整个组件树,重新渲染所有使用该属性的子组件。这种单向的数据流一方面比较清晰不容易混乱,另一方面是比较好维护,出了问题也比较好定位。
如何设计一个好组件
组件的主要目的是为了更好的复用,所以在设计组件的时候需要遵循高内聚低耦合的原则。
- 可以通过遵循几种设计模式原则来达到高复用的目的,比如单一职责原则:React推崇的是“组合”而非“继承”,所以在设计时尽量不设计大的组件,而是开发若干个单一功能的组件,重点就是每个组件只做一件事;开放/封闭原则,就是常说的对修改封闭,对扩展开放。在React中我们可以用高阶组件来实现。
- 使用高阶组件来实现组件的复用。高阶组件就是一个包装了另一个React组件的React组件,它包括属性代理(高阶组件操控着传递给被包裹组件的属性)和反向继承(实际上高阶组件继承被包裹组件)。我们可以用高阶组件实现代码复用,逻辑抽象。
- 使用容器组件来处理逻辑,展示组件来展示数据(也就是逻辑处理与数据展示分离)。比如可以在容器组件中进行数据的请求与处理,然后将处理后的数据传递给展示组件,展示组件只负责展示,这样容器组件和展示组件就可以更好地复用了。
- 编写组件代码时要符合规范,总之就是要可读性强、复用性高、可维护性好。
组件的render函数何时被调用
- 组件state发生改变时会调用render函数,比如通过setState函数改变组件自身的state值
- 继承的props属性发生改变时也会调用render函数,即使改变的前后值一样
- React生命周期中有个componentShouldUpdate函数,默认返回true,即允许render被调用,我们也可以重写这个函数,判断是否应该调用render函数
调用render时DOM就一定会被更新吗
不一定更新。
React组件中存在两类DOM,render函数被调用后, React会根据props或者state重新创建一棵virtual DOM树,虽然每一次调用都重新创建,但因为创建是发生在内存中,所以很快不影响性能。而 virtual dom的更新并不意味着真实DOM的更新,React采用diff算法将virtual DOM和真实DOM进行比较,找出需要更新的最小的部分,这时Real DOM才可能发生修改。
所以每次state的更改都会使得render函数被调用,但是页面DOM不一定发生修改。
组件的生命周期
组件生命周期有三种阶段:初始化阶段(Mounting)、更新阶段(Updating)、析构阶段(Unmouting)。
初始化阶段:
- constructor():初始化state、绑定事件
- componentWillMount():在render()之前执行,除了同构,跟constructor没啥差别
- render():用于渲染DOM。如果有操作DOM或和浏览器打交道的操作,最好在下一个步骤执行。
- componentDidMount():在render()之后立即执行,可以在这个函数中对DOM就进行操作,可以加载服务器数据,可以使用setState()方法触发重新渲染
组件更新阶段:
- componentWillReceiveProps(nextProps):在已挂载的组件接收到新props时触发,传进来的props没有变化也可能触发该函数,若需要实现props变化才执行操作的话需要自己手动判断
- componentShouldUpdate(nextProps,nextState):默认返回true,我们可以手动判断需不需要触发render,若返回false,就不触发下一步骤
- componentWillUpdate():componentShouldUpdate返回true时触发,在render之前,可以在里面进行操作DOM
- render():重渲染
- componentDidUpdate():render之后立即触发
组件卸载阶段:
- componentWillUnmount():在组件销毁之前触发,可以处理一些清理操作,如无效的timers等
- componentDidMount():卸载后立即触发
进行远程数据加载时,应该在哪个周期中完成
- 最好是在
componentDidMount
中进行异步请求。如果我们将ajax请求放在生命周期其他函数中,如constructor
或componentWIllMount
中,我们并不能保证请求仅在组件挂载完毕后才响应。如果我们的数据请求在组件挂载前就完成,并调用setState
函数将数据添加到组件状态中,对于未挂载的组件会报错。而在componentDidMount
中进行ajax请求能有效避免这个问题。
1Warning: setState(…): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. - React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到
componentWillMount
的触发次数。对于componentWillMount
这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用componentWillMount
。如果我们将 AJAX 请求放到componentWillMount
函数中,那么显而易见其会被触发多次,自然也就不是好的选择。[没深入]
顺便说说componentWillMount
函数,这个方法是在render
前立刻执行的,也是服务器渲染中唯一调用的钩子,其实除了同构的需求,通常情况下可以用constructor()
方法代替。
在哪些生命周期中可以修改组件的state
- componentDidMount和componentDidUpdate
- constructor、componentWillMount中setState会发生错误:setState只能在mounted或mounting组件中执行
- componentWillUpdate中setState会导致死循环
不同父节点的组件需要对彼此的状态进行改变时应该怎么实现
- 在没有Flux之前,Facebook推荐使用事件机制,但是一旦应用中这种需求增多,事件和回调会满天飞
- 传递接口,就是需要root传递两个接口给A和B,当A想改变B的状态时,A调用root传递给它的接口,然后这个接口再调用root传给B的接口(这个方法也很不科学)
- 用Flux管理状态
state里应该有什么
应该有啥:
- 事件函数可能进行修改的会导致UI进行渲染的数据
不应该有啥:
- 计算得出的值
- React组件
- props复制来的数据
如何对组件进行优化
- 使用上线构建(Production Build):会移除脚本中不必要的报错和警告,减少文件体积
- 避免重绘:重写shouldComponentUpdate函数,手动控制是否应该调用render函数进行重绘
- 尽可能使用Immutable Data:尽可能不修改数据,而是重新赋值数据。这样在检测数据对象是否发生修改方面会非常快,因为只需要检测对象引用即可,不需要挨个检测对象属性的更改
- 在渲染组件时尽可能添加key,这样virtual DOM在对比的时候就更容易知道哪里是修改元素,哪里是新插入的元素
组件中的key属性有什么用
React中的key是一个特殊的属性,它的出现不是给开发者用的,而是给React自己用的(给一个组件设置了key属性,并不能获取这个组件的key props)
React使用key来识别组件,它是一种身份标识,每一个key对应一个组件,react认为相同的key是同一个组件,这样后续相同的key就不会被创建
有了key属性后,就可以与组件建立一种对应关系,react根据key来决定是销毁重新创建组件还是更新组件:
- key相同:若组件属性有变化,react只更新对应的属性;没有变化则不更新
- key值不同:react会先销毁该组件,然后重新创建该组件
Component与Element和Instance的区别
- Element其实是一个纯粹的Object对象,用于描述在屏幕上看到的DOM节点,这个对象包括type、props、key和ref属性,但不包括DOM方法(React.createElement())
- Component是组件级别的类:接收参数并返回React元素的函数或类
- Instance:当使用ReactDOM.render()将一个组件渲染到一个具体的DOM元素中,返回的值就为一个实例
调用setState时,发生了什么事
调用setState时,react会做的第一件事就是将传递给setState的对象合并到组件的当前状态,然后会触发调和过程。经过调和过程,React会以相对高效的方式根据新的状态构建React元素树,并准备重新渲染整个UI界面。在React得到元素树后,React会通过diff算法算出新的树与老树之间的节点差异,然后根据差异对界面进行最小化重渲染。在diff算法中,React能够相对精确地算出哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
什么时候使用类组件(Class Component)而非功能组件(Functional Component)
若组件需要有state或需要使用生命周期,就用类组件,否则就用功能组件
什么是React的refs,为什么它们很重要
我们用render方法得到了组价的实例,然后就可以对它进行相关操作,但是在组件内,JSX是不会返回一个组件的实例,它只是一个ReactElement,只是告诉React被挂载的组件应该是长什么样。
refs是组件的一个很特殊的prop,可以附加到任何一个组件上,refs就是reference,组件被调用时会新建一个该组件的实例,refs就会指向这个实例
我们把refs放到原生的DOM组件input中,就可以通过refs得到DOM结点;如果把refs放到React组件中,就可以获得组件的实例,可以调用该组件的实例方法
受控组件(controlled component)和不受控组件(uncontrolled component)有什么区别
受控组件:在HTML中,标签\、\
不受控组件:表单数据不受setState控制,而是由DOM本身处理,与传统HTML表单输入相似,input输入值即显示最新值(使用ref从DOM获取表单值)
描述事件在React中的处理方式
React在virtual DOM的基础上实现了一个SyntheticEvent(合成事件)层,所有事件都绑定到最外层上。
在React底层,主要对合成事件做了两件事:事件委托和自动绑定。
事件委托:React的事件代理机制不会把事件处理函数直接绑定到真实的结点上,而是把所有事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。
自动绑定:在React组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。在使用ES6 classes和纯函数时,这种自动绑定就不存在了,需要我们手动绑定this:bind方法、双冒号语法、构造器内声明、箭头函数。