介绍

我在 2021 年作为一名应届毕业生加入了一家网络开发公司,担任前端工程师,并将在 2022 年进入我的第二年。

在实践中,我主要使用 React 和 TypeScript 进行前端开发。

这一次,我将总结现场小辈们提出的 React 技术问题。

另外,我不会以问答的形式回答问题,但我会深入解释。

本文的目标读者

  • 以前端工程师为目标的人
  • 从初学者到中级反应
  • 被问到 React 问题时不能很好地表达的人

本文的目标

  • 能够用语言表达 React 中常用的技术
  • 打破模糊的认识

承诺

  • 本文不是面试等问题模板合集。
  • 我在写这篇文章时对我的后辈在该站点提出的问题进行了深入的解释。

什么是 React 钩子?

反应钩子是官方文件解释如下。

Hooks 是 React 16.8 中添加的一个新特性。您可以使用 React 功能,例如状态,而无需编写类。

在这里,我们将详细解释 React hooks 中的 useStateuseEffect

说说useState的作用和行为

useState 是一个钩子,允许您在 React 中管理状态。

官方文件定义如下:

返回一个有状态的值和一个更新它的函数。
setState 函数用于更新状态。接收新的状态值并安排组件的重新渲染。

稍微分解一下useState的流程,会是这样。

  1. useState 允许连接 React 内部并维护状态
  2. useState 返回当前值和更新值的函数(更新函数)
  3. 将新值传递给useState 的更新函数告诉 React 重新运行(重新渲染)组件

    具体来说,我们将遵循 React 代码的流程。

    const Practice: NextPage = () => {
      const [value, setValue] = useState<string>("初期値");
      const onClick = () => {
        setValue("stateを更新しました");
      };
      console.log("レンダリング");
      return (
        <>
          <p>{value}</p>
          <button onClick={onClick}>stateを更新</button>
        </>
      );
    };
    
    1. 页面加载时首次渲染
    2. 使用 useState 使用 React 管理值的状态(带有初始值)
    3. 按钮单击将新值传递给更新函数
    4. 将传递给更新函数的值传递给 React,React 组件将重新渲染
    5. 屏幕刷新以显示传入的新值 3

      【初始状态】
      フロントエンド(React)の技術質問

      [更新后]
      フロントエンド(React)の技術質問

      请说说useEffect的作用和动作

      useEffect官方文件解释如下。

      使用这个钩子告诉 React 在渲染之后需要做一些事情。 React 会记住你传入的函数(我们称之为“副作用(函数)”)并在更新 DOM 后调用它。此副作用设置文档的标题,但它也可以检索数据或调用其他一些命令式 API。

      稍微分解一下,渲染结果反映到屏幕上后,可以使用useEffect执行useEffect中描述的副作用处理。

      这里副作用表示以下过程。

      • DOM 重写操作
      • 服务器通信(API 调用)
      • 重写变量

      useEffect在第一个参数中指定渲染时要执行的函数,并将依赖数据控制在第二个参数中的第一个参数中指定的函数。

      【初始渲染时向日志输出“Hello World”的过程】

      export const Parent: React.FC = () => {
        useEffect(() => {
          console.log("Hello World");
        }, []);
        return (
          <>
            <p>テスト</p>
          </>
        );
      };
      

      如上图,如果给useEffect的第二个参数的依赖数据传入一个空数组,则只能在第一次渲染时执行第一个参数中描述的函数。

      接下来考虑在第二个参数的依赖数组中指定值的情况

      [每次计数增加时执行useEffect内部的处理]

      export const Parent: React.FC = () => {
        const [count, setCount] = useState<number>(0);
        const addCount = () => {
          setCount(count + 1);
        };
        useEffect(() => {
          console.log("カウントが増えました");
        }, [count]);
        return (
          <>
            <button onClick={addCount}>クリック</button>
            <p>{count}</p>
          </>
        );
      };
      

      在这种情况下,每次单击按钮,您都可以看到useEffect 的第二个参数中指定的计数增加,并且执行了useEffect 中第一个参数中指定的进程。

      关于状态管理更新

      接下来,我们来看看前面介绍的useState上的常见问题。

      如何更新数组或对象的状态?

      初学者经常会在更新数组和对象的状态时卡住,让我们仔细看看。

      基于以下示例进行说明。

      • 在数组中准备一个名为 fruits 的状态
      • 单击按钮添加字符串“葡萄”并更新状态

      [如果数组更新不起作用]

      import React, { useState } from "react";
      
      export const Practice: React.FC = () => {
        const [fruits, setFruits] = useState<string[]>(["りんご", "ばなな"]);
        // 配列の最後にぶどうを追加する処理
        const onClick = () => {
          fruits.push("ぶどう");
          setFruits(fruits);
        };
        return (
          <>
            <p>{fruits}</p>
            <button onClick={onClick}>ぶどうを追加</button>
          </>
        );
      };
      

      在这种情况下,单击按钮不会更新屏幕上显示的值。

      可以看到 React 组件没有被重新渲染。

      フロントエンド(React)の技術質問

      原因是,官方文件当我检查 useState 时

      避免状态更新
      如果您使用与当前值相同的值进行更新,React 将退出而不渲染任何子级或执行副作用(React 使用 Object.is 的比较算法)。

      更新state的功能有“新状态"写入执行重新渲染,此时直接给已有的state赋值push,因此判断为相同值,避免渲染。

        const onClick = () => {
          // 直接stateに値をpushしても同じ値と検知される
          fruits.push("ぶどう");
          // 結果、更新関数を使ってもレンダリングが起きない
          setFruits(fruits);
        };
      

      传播语法通过使用现有数组的副本并将其传递给更新函数来触发渲染。

      [如果阵列更新成功]

        const onClick = () => {
          // スプレット構文で既存の値をコピー
          const copy = [...fruits];
          // コピーに対して値を追加
          copy.push("ぶどう");
          // 既存のstateとは異なる値(新しい値)が入ってくるのでレンダリングが起きる
          setFruits(copy);
        };
      

      フロントエンド(React)の技術質問

      顺便说一句,这个过程也可以写成如下。

        const onClick = () => {
          setFruits([...fruits, "ぶどう"]);
        };
      

      此外,如果您能阅读下面的文章,我会很高兴,因为它更详细地解释了这部分。

      关于数组和键

      为什么在 JSX 中使用数组进行循环时要指定键?

      我将解释为什么在jsx中使用数组时传递key的原因如下。

      [单击按钮时,将橙色添加到数组的开头]

      import React, { useState } from "react";
      import { Fruits } from "./fruits";
      
      export const Practice: React.FC = () => {
        const [fruits, setFruits] = useState<string[]>([
          "りんご",
          "ばなな",
          "ぶどう",
        ]);
      
        const onClick = () => {
          const copy = [...fruits];
          copy.unshift("みかん");
          setFruits(copy);
        };
      
        return (
          <>
            {fruits.map((i) => (
              <div key={i}>
                <Fruits name={i} />
              </div>
            ))}
            <button onClick={onClick}>みかんを追加</button>
          </>
        );
      };
      

      フロントエンド(React)の技術質問

      フロントエンド(React)の技術質問

      关于数组和键官方文件解释如下

      键帮助 React 识别哪些元素已被更改、添加或删除。数组中的每一项都应该被赋予一个键来给它一个稳定的身份。
      最好选择一个键作为在其兄弟项目中唯一标识项目的字符串。在大多数情况下,您会希望使用数据中的 ID 作为键

      指定密钥的原因官方文件解释如下。

      默认情况下,当递归遍历一个 DOM 节点的子节点时,React 只是简单地同时遍历两个子元素列表,每次发现差异时都会导致更新增加。

      我将使用图表来解释这部分。

      React 检测 React 元素树中的差异并更新 DOM。因此,渲染过程中的差异反映在浏览器中。

      如果密钥未通过,则流程运行如下。
      フロントエンド(React)の技術質問

      如果您不传递密钥,React 会将所有 React 元素识别为差异并将它们反映在 DOM 中。

      至于区别,虽然只是在前面加上了“橘子”,但“苹果、香蕉、葡萄”的位置也相应发生了变化,所以一切都被认定为区别了。

      另一方面,通过传递 key,即使 React 树的位置发生变化,也会被识别出来,只提取和更新差异,如下所示。

      フロントエンド(React)の技術質問

      综上所述,如果不指定key,不变的部分会被识别为差异,性能会变差。

      此外,建议指定一个可以唯一标识的值(ID 等)而不是索引作为 key 指定的值。

      这部分的原因请参考下面的文章。

      如何操作 DOM

      如何操作 DOM?

      您可以使用 useRef 进行 DOM 操作。

      官方文件那么useRef解释如下。

      useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数 (initialValue)。返回的对象在组件的生命周期内持续存在。
      本质上,useRef 就像一个“盒子”,可以在 .current 属性中保存一个可写值。

      我将把它分解一下,并用一个具体的例子来解释。

      [获取 JSX 中的输入并将其存储在变量中]

      import React, { useRef } from "react";
      
      export const Practice: React.FC = () => {
        const inputRef = useRef(null);
        console.log(inputRef);
      
        return (
          <>
            <input ref={inputRef} type="text" value={"refです"} />
          </>
        );
      };
      

      如果勾选console,可以确认input的DOM已经获取到了,如下图。
      フロントエンド(React)の技術質問

      基于此,我将编写代码用于以下处理。
      [点击按钮时关注输入表单的处理]

      export const Practice: React.FC = () => {
        // inputを取得する用
        const inputRef = useRef<any>();
        // ボタンをクリック時にinputにフォーカスを合わせる関数
        const onClick = () => {
          inputRef.current.focus();
        };
      
        return (
          <>
            <button onClick={onClick}>入力フォームにフォーカス</button>
            <br></br>
            <input ref={inputRef} type="text" onClick={onClick} />
          </>
        );
      };
      

      您可以检查单击按钮时输入表单是否实际获得焦点。

      フロントエンド(React)の技術質問

      useRefuseState 之间的区别是

      • useaRef 保留数据而不重新渲染
      • 当 Ref 改变时不要重新渲染(里面的数据已经改变)
      • useRef 用于将 ref 传递给 DOM 元素

      什么是函数式编程?

      我将解释函数式编程,这是你在使用 React 时应该知道的概念。

      函数式编程具有三个特点:

      • 状态管理与处理分离
      • 纯函数
      • 不变性

      让我们仔细看看每一个。

      状态管理与处理分离

      函数式编程对状态管理和处理进行分类和管理。

      我将通过查看 React 代码来检查它。

      [当你按下+1按钮时,1被添加到称为count的状态]

      import React, { useState } from "react";
      
      export const Practice: React.FC = () => {
        const [count, setCount] = useState<number>(0);
        const addCount = () => {
          setCount(count + 1);
        };
        return (
          <>
            <p>{count}</p>
            <button onClick={addCount}>+1</button>
          </>
        );
      };
      

      state 称为 count 在 React 中(状态管理) 并且函数组件Practice 返回 JSX (过程) 被分开。

      更新和管理state 在 React 内部完成,并与功能组件 (Practice) 分离。

      纯函数

      我将解释函数式编程的第二个特点:纯函数。

      纯函数具有以下性质:

      • 如果参数相同,则返回值将始终相同
      • 不要在函数外引用或更新状态(无副作用)
      • 不要更新作为参数传入的值

      首先,我们将检查是否满足上述条件。

      【如果函数外的状态被引用或更新(产生副作用)】

      let count = 0;
      
      export const Child: React.FC = () => {
        // 関数外の変数を更新
        count++;
        return <p>{count}</p>;
      };
      
      export const Parent: React.FC = () => {
        return (
          <>
            <Child />
            <Child />
          </>
        );
      };
      

      在函数外部定义的变量value 在子组件内部被引用和更新。

      因此,即使 Parent 组件调用同一个 Child 组件,也会根据调用顺序返回不同的结果。
      フロントエンド(React)の技術質問
      如果它不是这样的纯函数,则可能会产生意想不到的错误。

      [当它是纯函数时]

      type ChildProps = {
        count: number;
      };
      
      export const Child: React.FC<ChildProps> = ({ count }) => {
        return <p>{count}</p>;
      };
      
      export const Parent: React.FC = () => {
        return (
          <>
            <Child count={0} />
            <Child count={1} />
            <Child count={1} />
          </>
        );
      };
      

      フロントエンド(React)の技術質問

      在这种情况下,您可以确认将相同的参数 (count) 传递给 Child 组件会返回相同的结果。

      这种情况下的 Child 组件可以说是一个纯函数。

      通过像这样将 React 组件定义为纯函数,您可以防止出现意外错误。

      不变性

      不变性的特点如下。

      • 不要改变传入参数的值

      [当它不是不变性时]

      type ChildProps = {
        count: number;
      };
      
      export const Child: React.FC<ChildProps> = ({ count }) => {
        // 引数で渡ってきた値を更新している
        count = 30;
        return <p>{count}</p>;
      };
      
      export const Parent: React.FC = () => {
        return (
          <>
            <Child count={0} />
            <Child count={1} />
            <Child count={2} />
          </>
        );
      };
      

      由于props 传递的值count 在子组件内部更新,在这种情况下,它是一个不是**不可变**的函数。

      フロントエンド(React)の技術質問

      编写函数式编程的好处

      总结函数式编程,

      • 解耦状态管理和处理
      • 用纯函数消除副作用
      • 不变性

      通过像这样进行函数式编程

      • 可读性增加
      • 可重复使用
      • 易于编写测试
      • 易于模块化

      您可以获得以下好处

      在最后

      怎么样。

      这一次,我总结了 React 的技术问题。

      我在现场使用 Reac 进行开发,但是当我被问到一个技术问题时,我无法很好地表达出来,所以我再次重新组织了它。

      由于是对基本内容的解释,我想我会在下一篇文章中写下以下内容。

      • 性能优化
      • 关于全局状态管理
      • Next.js 相关

      我也在写关于前端的文章,所以如果你能读到我会很高兴。


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308626273.html

相关文章: