知识点

React实现一个场景,渲染多张图片,保证图片的底部尽量是对齐的,然后滚动条拉到底,在请求图片再次渲染

React 中 key 的用法,常用使用场景

key的概念

react 中的 key 属性是一个特殊的属性,它的出现不是给开发者用的,而是给 react 自身用的。
简单的来说,react 利用 key 来识别组件,他是一种身份标识,就像每个人的身份证一样。每个 key 对应一个组件,相同的 key, react 会认为是同一个组件,这样后续相同的 key 对应的组件都不会被创建。(经测试,16版本之后key重复也会渲染出来?)

key的使用场景

在项目开发中,key属性的使用场景最多的还是由数组动态创建的子组件情况,需要为每个子组件添加唯一的key属性值。那有的人会自然而然想到,key 和动态渲染的子元素获取的index的值很接近,是不是我们可以直接使用index值 赋值给key呢?

1
2
3
4
5
{
data.map((item, index) => (
<div key={index}>item.name</div>
))
}

在尝试后我们发现报错没了,渲染也没问题。但是这里我们强烈不推荐使用数组的index 值来作为key。
如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么所有元素都将重新渲染。

例如:
本来index=2 的元素向前移动后,那该元素的key 不也同样发生了改变,那这样改变,key 就没有任何存在的意义了,既然是作为身份证一样的存在,那就不容有失,当然,在你用key值创建子组件的时候,若数组的内容只是作为纯展示,而不涉及到数组的动态变更,其实是可以使用index 作为key的,

key的值必须保证唯一且稳定

我们在与key值打过几次交到以后,感觉key值就类似于数据库中的主键id一样,有且唯一。

React如何处理更新

React 内部 setState 是如何批处理的

setState 的批量更新按照先进先出的原则,顺序更新。

  1. 在 react 的 event handler 内部同步的多次 setState 会被 batch 为一次更新
  2. 在一个异步的事件循环里面多次 setState,react 不会 batch
  3. 可以使用React.unstable_batchedUpdates 来强制 batch

为什么在 setTimeout 中多次 setState,react 不会 batch?

因为 React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook (有点类似 koa 的 middleware),在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。

但是 setTimeout 这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。 React没有控制权的函数 setTimeout

为什么 react 要这么设计?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent() {
let [count, setCount] = useState(0);
return (
<div onClick={() => setCount(count + 1)}>
Parent clicked {count} times
<Child />
</div>
);
}

function Child() {
let [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Child clicked {count} times
</button>
);
}

上面这样的 demo,由于点击事件冒泡的缘故,我们假设如果 react 不 batch 立即更新的话,那么点了 child button 之后的逻辑会是如下这样

1
2
3
4
5
6
7
8
9
*** 进入 react click 的事件函数 ***
Child (onClick) 触发点击
- setState 修改 state
- re-render Child 重新渲染 // 😞 不必要的
Parent (onClick) 触发点击(冒泡)
- setState 修改 state
- re-render Parent 重新渲染
- re-render Child 重新渲染 (渲染是自顶向下的,父亲更新会导致儿子更新)
*** 退出 react click 的事件函数 ***

从上面可以看出,第一次子组件的重新渲染完全是浪费的。

所以 React 设计成 setState 不立即触发重新渲染,而是先执行完所有的 event handler,然后用一次重新渲染完成所有更新。

forceUpdate的说明

forceUpdate 从函数名上理解:“强制更新”。

  1. forceUpdate 是同步的吗?“强制”会保证调用然后直接dom-diff吗?

forceUpdate在批量与否的表现上,和setState是一样的。在React有控制权的函数里,是批量的。

  1. “强制”更新整个组件树吗?包括自己,子孙后代组件吗?

forceUpdate只会强制本身组件的更新,即不调用“shouldComponentUpdate”直接更新,对于子孙后代组件还是要调用自己的“shouldComponentUpdate”来决定的。

React15如何去优化

useCallback useMemo的区别

useMemo

1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

把创建函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized值。这种优化有助于避免在每次渲染时都进行高开销的计算。

useCallback

1
2
3
4
const memoizedCallback = useCallback(
() => doSomething(a, b),
[a, b]
);

把内联回调函数及依赖项数组作为参数传入useCallback, 它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数
传递给经过优化的并使用引用相等性去避免非必要的渲染的子组件时,它将非常有用

看起来似乎和useMemo差不多,我们来看看有什么异同:

useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果,useCallback返回的函数。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

React Hooks

没有破坏性改动

  • 完全是可选的。
  • 100%向后兼容的。
  • 现在可用,hook 发布于 v16.8.0

没有计划从React移除class。
Hook不会影响你对 React 概念的理解。

动机

Hook 解决了我们五年来编写和维护成千上万的组件时遇到的各种各样看起来不相关的问题。

在组件之间复用状态逻辑很难

你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。

复杂组件变得难以理解

我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

难以理解的 class

除了代码复用和代码管理会遇到困难外,我们还发现 class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。class 也给目前的工具带来了一些问题。例如,class 不能很好的压缩,并且会使热重载出现不稳定的情况。因此,我们想提供一个使代码更易于优化的 API。

为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。

React Context 如何去使用

  • React.createContext
1
const MyContext = React.createContext(null);
  • Context.Provider
1
<MyContext.Provider value={value}>

每个 Context 组件都会返回一个 Provider React组件,它允许消费组件订阅 context 的变化。

  • Class.contextType
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Myclass extends React.Component {
static contextType = MyContext;

componentDidMount() {
let value = this.context;
}

componentDidUpdate() {
let value = this.context;
}

render() {
let value = this.context;
}
}

挂载在class上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。 这能让你使用
this.context 来消费最近 Context 上的值。你可以在任何生命周期中访问到它,包括render函数中。

  • Context.Consumer
1
2
3
4
5
<MyContext.Consumer>
{
value => /* 基于context的值渲染 */
}
</MyContext.consumer>

Redux 和 Mobx 的区别

  1. 开发难度低,redux 需要引入很多第三方库来完善工程需求。
  2. 开发代码少, redux 需要写大量的样板代码。
  3. 增加渲染性能,redux需要借助 shouldComponentUpdate 或者 immutable 来优化。

在使用Redux中,当修改一个数据,发现组件没有更新,可能的原因有哪些

  1. 是否 key 值重复导致
  2. 是否正确的引入数据, 或者传递props
  3. 是否组件内部忘记connect

React16 函数式编程怎么去优化

  • useMemo
  • useCallback
  • fragments