# react 相关

# 概念

官方指南 (opens new window)

在 react 应用程序由两大重要组成:元素和组件。

# 元素

React 元素是构成 React 应用的最小砖块,描述了屏幕上看到的内容(DOM)。React 元素开销极小,React 保证了 React 元素与 DOM 元素的一致。

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。更新 UI 唯一的方式是创建一个全新的元素。

# JSX 语法

JSX 是 JavaScript 的扩展,将 UI 和 逻辑 结合起来,有利于描述页面应有的交互本质。React 负责将这些语法生成元素。 Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

// 注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};
1
2
3
4
5
6
7
8

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,通过将元素和逻辑共同存放在 组件 中,以实现关注点分离。

关注点分离:日常生活中解决复杂问题的一种系统思维方法。思路是,先将复杂问题做合理的分解,再分别仔细研究问题的不同侧面 (关注点),最后综合各方面的结果,合成整体的解决方案。

JSX 本质上是一个表达式。

# JSX 能够防注入攻击

React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。

# 组件

从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

根据组件的定义方式可分为函数组件和类组件。

React 内部对两种组件的处理:

// 如果 Greeting 是一个函数
const result = Greeting(props); // <p>Hello</p>

// 如果 Greeting 是一个类
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
1
2
3
4
5
6

# 函数组件与类组件

Hook 会因为在渲染时创建函数而变慢吗?——官方指南 (opens new window)

从官方的观点,应该是更推荐使用 函数组件。

  • Hook 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。
  • 没有计划从 React 中移除 class……
  • 我们准备让 Hook 覆盖所有 class 组件的使用场景,但是我们将继续为 class 组件提供支持。

# 纯组件

纯函数:不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。为了实现 UI 交互,组件可以维护自有的状态 state 。

state 的更新是异步的,因为 React 为了提升性能,会收集多个 setState 一次更新。

# 与元素的区别

一个 Element 是一个简单的对象,它描述了你希望在屏幕上以 DOM 节点或其他组件的形式呈现的内容。

而一个组件可以用多种不同方式声明。它可以是一个含有 render() 方法的类。或者,在简单的情况中,它可以定义为函数。无论哪种情况,它都将 props 作为输入,并返回一个 JSX 树作为输出。

# 生命周期

# 组合代替继承

# Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 设计目的是为了共享那些对于一个组件树而言是 “全局” 的数据,例如当前认证的用户、主题或首选语言。

也有一些其它方案可以解决父组件向深嵌套的子组件传递 prop 的方案:

  • 组件组合:把子组件定义在父组件内,然后传递子组件本身
  • 使用 render prop 以加强父子组件间的渲染通信

有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据向组件树下所有的组件进行 “广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

# HOOK

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性,是一些可以让你在函数组件里 “钩入” React state 及生命周期等特性的函数。

# 为什么使用 HOOK

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

React 没有提供将可复用性行为 “附加” 到组件的途径,你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

  1. 复杂组件变得难以理解

组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。

在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。

  1. 难以理解的 class

你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

class 不能很好的压缩,并且会使热重载出现不稳定的情况。

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

# 使用规则

  • 只能在函数最顶层调用 Hook。不要在循环、条件判断或者子函数中调用。
    • 如果需要条件判断,可以在 Hook 内部实现
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
  • Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

# 使用多个 useState 时,React 怎么知道哪个是哪个

React 使用 Hook 函数的调用顺序来保证对应关系。因为通过上述规则,保证了在每次渲染中,Hook 的调用顺序都是相同的。

# 常用 HOOK

# useState

  • 参数:初始 state
  • 返回值:当前 state 以及更新 state 的函数

State 变量可以很好地存储对象和数组,因此,你仍然可以将相关数据分为一组。然而,不像 class 中的 this.setState,更新 state 变量总是替换它而不是合并它

state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。否则它就不是 “state” 了!

# useEffect

副作用:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。

副作用分为两种:需要清除的、不需要清除的。

当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的 “副作用” 函数。整合了 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期函数。

有一些副作用,我们希望它在每次渲染之后执行 —— 但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方(componentDidMount、componentDidUpdate)调用它。

通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。

  • 默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
  • 可以访问到组件的 props 和 state
  • 通过返回一个函数来指定如何 “清除” 副作用
  • React 会在执行当前 effect 之前对上一个 effect 进行清除。
    • 目的是为了减少由于 prop 更新带来的数据不一致(effect 覆盖了 componentDidUpdate,却没法在 update 阶段手动更新 state)

注意

传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的 count 的值(重新生成 effect 函数,产生新闭包,包进最新的 state),而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于” 一次特定的渲染。

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。

进阶

可以使用多个 useEffect ,将不相关逻辑分离到不同的 effect 中,实现关注点分离。(又一个抛弃 class 组件的理由)React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

可以向 useEffect 传入第二个数组参数以优化渲染频次。数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

关于这种优化方式有一些 注意事项 (opens new window)

# 自定义 HOOK

Hook 是一种复用状态逻辑的方式,它不复用 state 本身。

Hook 的每次调用都有一个完全独立的 state

你可以创建涵盖各种场景的自定义 Hook,如表单处理、动画、订阅声明、计时器,甚至可能还有更多我们没想到的场景。

// 自定义的例子,订阅用户状态
import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用自定义 hook
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
1
2
3
4
5
6
7
8
9

约定

  • 自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么
  • 自定义 Hook 应当以 use 开头,以便 React 检查其是否符合 Hook 规则
  • 自定义 Hook 中的 state 和 effect 都是完全隔离的

# useRedux

redux——中文翻译 (opens new window)

# 八股

React 面试题 & 回答 (opens new window)

# React 的特性

  • 虚拟 DOM
  • 服务端渲染
  • 单向数据流、数据绑定
  • 可复用/可组合的组件式 UI 开发视图

#

# 原理

# 最佳实践

# MVVM

  • React 与 jQuery 的区别是什么?
  • 组件完整的生命周期
  • React 里 setState 方法是同步的还是异步的?
  • 组件属性受控和不受控是什么意思?
  • React 里组件间通信用什么方案?
  • React 高阶组件(HOC)是什么,在什么场景使用?有什么副作用?
  • 什么是 Stateless Component?
  • React 如何做性能优化?shouldComponentUpdate
  • 父组件如何改变内部子组件的渲染逻辑?
    • this.props.children
    • React.Children.map
    • React.cloneElement
  • 单页面应用:React-Router
    • /1 跳转到 /2 时页面组件是否会被重新初始化渲染
    • 如何实现类打点的全局需求,onEnter
    • 页面 bundle 太大有什么解决方案
  • React 服务端渲染
  • Vue 双向绑定实现原理
    • getter/setter + 劫持数组方法
    • 还有哪些方案?脏检查/Proxy,三个方案优缺点
    • vue 里在 data 上添加一个新属性 view 是否会得到响应?不会,为什么?怎么解决?
  • Vue 2.0 引入 vdom 的原因是什么
    • vdom 有哪些优势:diff 性能、标准化数据适配多端渲染
最后更新时间: 8/31/2021, 10:32:34 AM