# React
# 1 Redux 中 reducer 不能做异步操作的原因
- 先从 Redux 的设计层面来解释为什么 Reducer 必须是纯函数
如果你经常用 React + Redux
开发,那么就应该了解 Redux
的设计初衷。 Redux
的设计参考了 Flux
的模式,作者希望以此来实现时间旅行,保存应用的历史状态,实现应用状态的可预测。所以整个 Redux
都是函数式编程的范式,要求 reducer
是纯函数也是自然而然的事情,使用纯函数才能保证相同的输入得到相同的输出,保证状态的可预测。所以 Redux
有三大原则:
- 单一数据源,也就是
state
state
是只读,Redux
并没有暴露出直接修改state
的接口,必须通过action
来触发修改- 使用纯函数来修改
state
,reducer
必须是纯函数
- 下面 再从代码层面来解释为什么 reducer 必须是纯函数
那么 reducer 到底干了件什么事, 在 Redux 的源码中只用了一行来表示:
currentState = currentReducer(currentState, action)
这一行简单粗暴的在代码层面解释了为什么 currentReducer
必须是纯函数。 currentReducer
就是我们在 createStore
中传入的 reducer
(至于为什么会加个 current
有兴趣的可以自己去看源码 ),reducer
是用来计算 state
的,所以它的返回值必须是 state
, 也就是我们整个应用的状态,而不是 promise
之类的。
要在 reducer
中加入异步的操作,如果你只是单纯想执行异步操作,不会等待异步的返回,那么在 reducer
中执行的意义是什么。如果想把异步操作的结果反映在 state
中,首先整个应用的状态将变的不可预测, 违背 Redux
的设计原则,其次,此时的 currentState
将会是 promise
之类而不是我们想要的应用状态,根本是行不通的。
# 2 React 中 fiber 是用来做什么的
因为 Javascript 单线程的特点,每个同步任务不能耗时太长,不然就会让程序不会对其他输入做出相应操作,React 的更新过程就是犯了这个禁忌,而 React Fiber 就是要改变现状。而可以通过分片来破解 Javascript 中同步操作时间过长的问题。
把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务易燃有运行的机会。
React Fiber 把更新过程碎片化,每执行完一段更新过程,就把控制权交还给 React 负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。
维护每一个分片的数据结构,就是 Fiber。
# 3 React 生命周期
初始化阶段:
getInitialState
获取每个实例的初始化状态getDefaultProps
获取实例的默认属性componentWillMount
组件即将被装载、渲染到页面上render
组件在这里生成虚拟的DOM节点
运行中状态:
componentWillReceiveProps
组件将要接收到属性的时候调用shouldComponentUpdate
组件接收到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用, 后面的函数不会被继续执行了)componentWillUpdate
组件即将更新不能修改属性和状态render
组件重新描绘componentDidUpdate
组件已经更新完成
卸载过程:
componentWillUnmount
组件即将销毁
# 4 shouldComponentUpdate 函数有什么作用
shouldComponentUpdate
是一个允许我们自行决定某些组件(以及他们的子组件)是否进行更新的生命周期函数,Reconciliation
的最终目的是尽可能以最有效的方式去根据新的 state
更新UI,如果你已经知道 UI 的哪些状态无需进行改变,就没必要去让 React
去判断它是否该改变。让 shouldComponentUpdate
返回 false
, React
就会让当前的组件和其子组件保持不变。
# 5 当组件的setState函数被调用之后,发生了什么
在代码中调用 setState
函数之后, React
会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
经过调和过程, React
会以相对高效的方式根据新的状态构建 React
元素数并且着手重新渲染整个UI界面。在 React
得到元素树之后, React
会自动计算出新的树和老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中, React
能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
# 6 在生命周期中的哪一步你应该发起 AJAX 请求
React
下一代调和算法 Fiber
会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount
的触发次数。对于 componentWillMount
这个生命周期的调用次数会变得不确定,React
可能会多次频繁调用 componentWillMount
。 如果我们将 AJAX
请求放到 componentWillMount
函数中, 那么显而易见其会被触发多次,自然也就不是好的选择。
如果我们将 AJAX
请求放置到生命周期的其他函数中, 我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了 setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount
函数中进行 AJAX
请求则能有效避免这个问题。
# 7 createElement 与 cloneElement 的区别是什么
createElement 函数是 JSX 编译之后使用的创建 React Element 的 函数
, 而 cloneElement 则是用于复制某个 元素
并传入新的 Props。
# 8 传入 setState 函数的第二个参数的作用是什么
该函数会在 setState
函数调用完成并且组件开始重渲染的时候被调用,我们可以用来该函数来监听渲染是否完成:
this.setState(
{
username: 'fangyuan'
},
() => console.log('setState has finished and the component has re-rendered.')
)
# 9 浅谈 useEffect
一、说说 useEffect, 什么是副作用? 第二个参数为 【】 是什么意思?
1.什么是 useEffect
React 官方文档解释:Effect Hook 可以让你在函数组件中执行 副作用 操作
2.什么是副作用
副作用(side effect): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用。
因为我们渲染出的页面都是静态的,任何在其之后的操作都会对它产生影响,所以称之为副作用。
副作用又分为两种:(1)无需清楚的副作用 (2)需要清楚的副作用
(1)无需清楚的副作用:
有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更DOM,记录日志,这些都是常见的无需清楚的操作。因为我没在执行完这些操作之后,就可以忽略他们了。
(2)需要清除的副作用: 之前,我们研究了如何使用不需要清除的副作用,还有一些副作用的需要清除的。例如外部数据源,添加DOM事件。这种情况下,清除工作是非常重要的,可以防止引起内存泄露
在这个例子中,给鼠标的click事件添加了一个监听器,
import React, { useState, useEffect } from 'react'
const MouseTracker: React.FC = () => {
const [ positions, setPositions ] = useState({ x: 0, y: 0 })
useEffect(() => {
const updateMouse = (e: mouseEvent) => {
console.log('inner')
setPositions({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse)
})
return (
<p>X: {positions.x}, Y: {positions.y}</p>
)
}
export default MouseTracker
点击鼠标,会在屏幕打印出鼠标当前的位置,并在控制台打印出inner。但是这里存在一个问题,打印inner的次数并不是线性增加(点一次增加一次),而是点一次,inner被打印了多次。这是因为我们在每次更新渲染页面的时候都会调用 useEffect 的回调函数,这样就会添加越来越多的 click 时间,而没有清楚。
如何清除?
import React, { useState, useEffect } from 'react'
const MouseTracker: React.FC = () => {
const [ positions, setPositions ] = useState({ x: 0, y: 0})
useEffect(() => {
const updateMouse = (e: MouseEvent) => {
console.log('inner')
setPositions({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse)
return () => {
document.removeEventListener('click', updateMouse)
}
})
return (
<p>X: {positions.x}, Y: {positions.y}</p>
)
}
export default MouseTracker
可以发现,对比之前的代码,这里多 return 一个函数, 可以在这个函数中做清除操作, 现在点鼠标之后,inner被打印的次数就是线性增加,点一次,inner被打印一次,而不是被打印多次。
3. useEffect的第二个参数? (控制 useEffect 的执行)
第一个参数为一个函数: 这个函数的目的就是为了告诉 React 组件需要在渲染后执行哪些操作,这个函数会在DOM更新之后被调用,use Effect 默认在每次渲染之后都会执行,但是也可以手动控制它的执行。
第二个参数(一个数组): 当数组中的任意一项变化的时候,useEffect会被重新执行。 如果传递一个空数组 [],告诉 useEffect 不依赖于 state、props中任意值,useEffect就只会运行一次。
如果数组中有值
import React, { useState, useEffect } from 'react' const LikeButton: React.FC = () => { const [ like, setLike ] = useState(0) const [on, setOn ] = useState(true) useEffect(() => { console.log('document title effect is running') document.title = `点击了${like}次` }, [like]) return ( <> <button onClick={()=>{setLike(like + 1)}}>{like}</button>. <button onClick={()=>{setOn(!on)}}>{on ? 'ON' : 'OFF'}</button> </> ) }
这里的依赖为 like, 只有当 like 变化时, useEffect 才会执行, 而当 on 变化时, useEffect 不会执行.