react教程要点记录

描述UI

  • React 组件是一段可以 使用标签进行扩展 的 JavaScript 函数。
  • React 组件是常规的 JavaScript 函数,但 组件的名称必须以大写字母开头
  • 同一文件中,有且仅有一个默认导出,但可以有多个具名导出!
  • 为什么需要一个根标签
    • JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。
  • 切勿将数字放在 && 左侧.左侧为0则会渲染为0
  • key 需要满足的条件
    • key 值在兄弟节点之间必须是唯一的。 不过不要求全局唯一,在不同的数组中可以使用相同的 key。
    • key 值不能改变,否则就失去了使用 key 的意义!所以千万不要在渲染时动态地生成 key。
  • 为什么需要key
    • React 里需要 key 和文件夹里的文件需要有文件名的道理是类似的。一个精心选择的 key 值所能提供的信息远远不止于这个元素在数组中的位置。即使元素的位置在渲染的过程中发生了改变,它提供的 key 值也能让 React 在整个生命周期中一直认得它。
    • 请不要在运行过程中动态地产生 key,像是 key={Math.random()} 这种方式。这会导致每次重新渲染后的 key 值都不一样,从而使得所有的组件和 DOM 元素每次都要重新创建。这不仅会造成运行变慢的问题,更有可能导致用户输入的丢失。
  • 纯函数的基本定义:
    • 只负责自己的任务。 它不会更改在该函数调用前就已存在的对象或变量。
    • 输入相同,输出也相同。 在输入相同的情况下,对纯函数来说应总是返回相同的结果。
  • React 假设你编写的所有组件都是纯函数。React 的渲染过程必须自始至终是纯粹的。组件应该只返回它们的 JSX,而不改变在渲染前,就已存在的任何对象或变量 — 这将会使它们变得不纯粹!
  • React 提供了 “严格模式”,在严格模式下开发时,它将会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件。
  • 副作用:包括更新屏幕、启动动画、更改数据等,它们被称为 副作用。它们是 “额外” 发生的事情,与渲染过程无关。
  • 在 React 中,副作用通常属于 事件处理程序。事件处理程序是 React 在你执行某些操作(如单击按钮)时运行的函数。即使事件处理程序是在你的组件 内部 定义的,它们也不会在渲染期间运行! 因此事件处理程序无需是纯函数
  • 如果你用尽一切办法,仍无法为副作用找到合适的事件处理程序,你还可以调用组件中的 useEffect 方法将其附加到返回的 JSX 中。这会告诉 React 在渲染结束后执行它。然而,这种方法应该是你最后的手段

添加交互

  • 事件处理函数是执行副作用的最佳位置,数据的改变使用state存储

  • 为什么需要useState

    • 局部变量无法在多次渲染中持久保存。当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
    • 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
  • Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最顶层调用。

  • 如果你渲染同一个组件两次,每个副本都会有完全隔离的 state

  • React 把更改提交到 DOM 上

    • 对于初次渲染, React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
    • 对于重渲染, React 将应用最少的必要操作(在渲染时计算!),以使得 DOM 与最新的渲染输出相互匹配。
  • React 仅在渲染之间存在差异时才会更改 DOM 节点

  • 在渲染完成并且 React 更新 DOM 之后,浏览器就会重新绘制屏幕

  • 在一个 React 应用中一次屏幕更新都会发生以下三个步骤:

    1. 触发
    • 组件的 初次渲染。
    • 组件(或者其祖先之一)的 状态发生了改变
    1. 渲染。在您触发渲染后,React 会调用您的组件来确定要在屏幕上显示的内容。“渲染中” 即 React 在调用您的组件。
    • 在进行初次渲染时, React 会调用根组件。
    • 对于后续的渲染, React 会调用内部状态更新触发了渲染的函数组件。
    1. 提交。在渲染(调用)您的组件之后,React 将会修改 DOM。
    • 对于初次渲染, React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
    • 对于重渲染, React 将应用最少的必要操作(在渲染时计算!),以使得 DOM 与最新的渲染输出相互匹配。
  • 设置 state 会触发渲染(当你调用 useState 时)

  • 渲染会及时生成一张快照

    • “正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。
    • 返回的 UI “快照”是可交互的。它其中包括类似事件处理函数的逻辑,这些逻辑用于指定如何对输入作出响应。React 随后会更新屏幕来匹配这张快照,并绑定事件处理函数。因此,按下按钮就会触发你 JSX 中的点击事件处理函数。
    • state 实际上“活”在 React 本身中——就像被摆在一个架子上!——位于你的函数之外。当 React 调用你的组件时,它会为特定的那一次渲染提供一张 state 快照。你的组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!
  • 设置 state 只会为下一次渲染变更 state 的值。

  • 一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。

  • React 会使 state 的值始终”固定“在一次渲染的各个事件处理函数内部。 你无需担心代码运行时 state 是否发生了变化。

  • 变量和事件处理函数不会在重渲染中“存活”。每个渲染都有自己的事件处理函数。

  • 每个渲染(以及其中的函数)始终“看到”的是 React 提供给这个 渲染的 state 快照。

  • 过去创建的事件处理函数拥有的是创建它们的那次渲染中的 state 值。

  • React 会等到事件处理函数中的 所有 代码都运行完毕再处理你的 state 更新。

  • 在下次渲染前多次更新同一个 state,需要传入一个更新函数,如setNumber(n => n + 1),而不是像 setNumber(number + 1) 这样传入 下一个 state 值

  • 设置 state 不会更改现有渲染中的变量,但会请求一次新的渲染。

  • React 会在事件处理函数执行完成之后处理 state 更新。这被称为批处理。

  • 要在一个事件中多次更新某些 state,你可以使用 setNumber(n => n + 1) 更新函数。

  • 把所有存放在 state 中的 JavaScript 对象都视为只读的,为了真正地 触发一次重新渲染你需要创建一个新对象并把它传递给 state 的设置函数

  • 不要直接修改一个对象,而要为它创建一个 新 版本,并通过把 state 设置成这个新版本来触发重新渲染。

  • 你可以使用这样的 {...obj, something: 'newValue'} 对象展开语法来创建对象的拷贝。

状态管理

  • 只要一个组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。 如果它被移除,或者一个不同的组件被渲染在相同的位置,那么 React 就会丢掉它的 state。
  • 相同位置的相同组件会使得 state 被保留下来
  • 对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置!
  • 当你在相同位置渲染不同的组件时,组件的整个子树都会被重置
  • 永远要将组件定义在最上层(函数组件外)并且不要把它们的定义嵌套起来,否则每次渲染出的都是不同的组件,状态会丢失
  • 在相同位置重置 state
    1. 将组件渲染在不同的位置。同一个组件在第一个位置与第二个位置交替展示时会重置state
    2. 使用 key 来重置 state。即使两个 <Counter /> 会出现在 JSX 中的同一个位置,它们也不会共享 state
    • 使用key后,React 将重新创建 DOM 元素,而不是复用它们。
  • 为被移除的组件保留 state
    1. 用 CSS 把其他组件隐藏起来。这些组件就不会从树中被移除了,所以它们的内部 state 会被保留下来。这种解决方法对于简单 UI 非常有效。但如果要隐藏的树形结构很大且包含了大量的 DOM 节点,那么性能就会变得很差。
    2. 你可以进行状态提升并在父组件中保存每个收件人的草稿消息。这是最常见的解决方法
    3. 你也可以使用localStorage存储草稿信息
    4. 为多个 不同位置的 相同组件 指定key可以保留state
  • 把 useState 转化为 useReducer:
    1. 通过事件处理函数 dispatch actions(派发action对象);
    2. 编写一个 reducer 函数,它接受传入的 state 和一个 action对象(通常包含type字段),并返回一个新的 state;
    3. 使用 useReducer 替换 useState;
  • reducer 必须是一个纯函数——它应该只计算下一个状态。而不应该 “做” 其它事情,包括向用户显示消息。这应该在事件处理程序中处理。(为了便于捕获这样的错误,React 会在严格模式下多次调用你的 reducer。
  • Context 可以让父节点,甚至是很远的父节点都可以为其内部的整个组件树提供数据。
    1. 通过 export const MyContext = createContext(defaultValue) 创建并导出 context。
    2. 在无论层级多深的任何子组件中,把 context 传递给 useContext(MyContext) Hook 来读取它。
    3. 在父组件中把 children 包在 <MyContext.Provider value={...}> 中来提供 context。
  • 在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中。
  • 不同的 React context 不会覆盖彼此。你通过 createContext() 创建的每个 context 都和其他 context 完全分离,只有使用和提供 那个特定的 context 的组件才会联系在一起。一个组件可以轻松地使用或者提供许多不同的 context。

应急方案

  • 当你希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,你可以使用 ref
  • 当一条信息用于渲染时,将它保存在 state 中。当一条信息仅被事件处理器需要,并且更改它不需要重新渲染时,使用 ref 可能会更高效。
  • 何时使用ref
    • 存储timeout ID
    • 存储和操作DOM 元素
    • 存储不需要被用来计算 JSX 的其他对象。
    • 不要在渲染过程中读取或写入 ref.current,ref 本身是一个普通的 JavaScript 对象
  • 给未知长度的列表中每项绑定ref
    • 将函数传递给 ref 属性。这称为 ref 回调。当需要设置 ref 时,React 将传入 DOM 节点来调用你的 ref 回调,并在需要清除它时传入 null 。这使你可以维护自己的数组或 Map,并通过其索引或某种类型的 ID 访问任何 ref。
  • 访问另一个组件的 DOM 节点
    • 一个组件可以指定将它的 ref “转发”给一个子组件,子组件是使用 forwardRef 声明的。 这让从父组件接收的ref作为第二个参数传入组件,第一个参数是 props
  • 使用ref后,父组件可以调用子组件dom节点的所有方法,使用命令句柄(useImperativeHandle)可以只暴露一部分 API
  • 通常,你将从事件处理器访问 refs。
  • 你可以强制 React 同步更新(“刷新”)DOM(添加一个元素后滚动至该元素)。 为此,从 react-dom 导入 flushSync 并将 state 更新包裹 到 flushSync 调用中
  • useEffect 会把它包裹的代码放到屏幕更新渲染之后执行
    -为什么依赖数组中可以省略 ref 或者 set函数?
  • React 总是在执行下一轮渲染的 Effect 之前清理(执行effect中return出的清理函数)上一轮渲染的 Effect。
  • 如果一个值可以基于现有的 props 或 state 计算得出,不要把它作为一个 state,而是在渲染期间直接计算这个值
  • 你可以使用 useMemo Hook 缓存(或者说 记忆(memoize))一个昂贵的计算(用该hook包裹一个耗时的函数,以避免每次渲染期间的重新计算)。
  • 当 props 变化时重置所有 state,应当给该组件设置key属性,key变化,react会自动重置该组件
  • 当 props 变化时重置部分 state
    • 在渲染期间更新组件时,React 会丢弃已经返回的 JSX 并立即尝试重新渲染。为了避免非常缓慢的级联重试,React 只允许在渲染期间更新 同一 组件的状态。如果你在渲染期间更新另一个组件的状态,你会看到一条报错信息。
  • 如果某些逻辑必须在 每次应用加载时执行一次,而不是在 每次组件挂载时执行一次,可以添加一个顶层变量(写在组件函数之外)来记录它是否已经执行过了
  • 订阅外部 store,useSyncExternalStore
  • 数据请求中,为了修复竞态条件下返回值set错误的问题,你需要添加一个 清理函数 来忽略较早的返回结果
  useEffect(() => {
    let ignore = false;
    fetchResults(query, page).then(json => {
      if (!ignore) {
        setResults(json);
      }
    });
    return () => {
      ignore = true;
    };
  }, [query, page]);
  • 每个 Effect 表示一个独立的同步过程。抵制将与 Effect 无关的逻辑添加到已经编写的 Effect 中(代码中的每个 Effect 应该代表一个独立的同步过程),仅仅因为 这些逻辑 需要与 Effect 同时运行(应重新写一个effect函数来同步 这些逻辑,它的的好处是,删除一个 Effect 不会影响另一个 Effect 的逻辑)。
  • 组件内部的所有值(包括 props、state 和组件体内的变量)都是响应式的。任何响应式值都可以在重新渲染时发生变化,所以需要将响应式值包括在 Effect 的依赖项中。
  • 避免将对象和函数作为依赖项。如果在渲染过程中创建对象和函数,然后在 Effect 中读取它们,它们将在每次渲染时都不同。这将导致 Effect 每次都重新同步
  • 避免禁用检查工具。为了不违反规则,修复代码总是值得的!
useEffect(() => {
  // ...
  // 🔴 避免这样禁用静态代码分析工具:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);
  • 使用 useEffectEvent (实验性的功能)这个特殊的 Hook 从 Effect 中提取非响应式逻辑。Effect Event。它是 Effect 逻辑的一部分,但是其行为更像事件处理函数。它内部的逻辑不是响应式的,不需要添加到effect的依赖中,Effect Event 让你在 Effect 响应性和不应是响应式的代码间“打破链条”, Effect Event 读取的是最新的 props 和 state 。
  • 事件处理函数内部的逻辑是非响应式的(用户的特定操作,才会执行)。
  • Effect 内部的逻辑是响应式的(切换聊天室,需要自动断开之前的,连接最新的)。
  • 你可以将非响应式逻辑从 Effect 移到 Effect Event 中。
  • 只在 Effect 内部调用 Effect Event。
  • 不要将 Effect Event 传给其他组件或者 Hook。
  • 注意,你不能“选择” Effect 的依赖。每个被 Effect 所使用的响应式值,必须在依赖中声明。依赖是由 Effect 的代码决定的。每个 Effect 应该代表一个独立的同步过程,不应该同步两个不同的、不相关的东西,应将他们拆分在不同的effect中
  • 是否在读取一些状态来计算下一个状态?使用state的更新函数写法
  • 你想读取一个值而不对其变化做出“反应”吗?或者来自 props 的事件处理程序的处理,将非响应式逻辑(props的事件处理程序)移至 Effect Event 中(非响应式逻辑应该在事件中)
  • 尽量避免对象和函数依赖。将它们移到组件外(常量对象或者函数)、effect外(在effect外对对象或者函数解构,使effect依赖解构出来的基本类型的值)、 Effect 内(在effect中创建对象或者函数)
  • 自定义 Hook 共享的只是状态逻辑而不是状态本身。对 Hook 的每个调用完全独立于对同一个 Hook 的其他调用
  • 不要创建像 useMount 这样的自定义 Hook。保持目标具体化。

api

  • 即使一个组件被记忆化(memo)了,当它自身的状态发生变化时,它仍然会重新渲染
  • 即使组件已被记忆化(memo),当其使用的 context 发生变化时,它仍将重新渲染
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容