React技术前端的问题
首先
我在2021年以新人身份加入了一家Web开发公司,担任前端工程师的职位,现在已经是2022年的第二年了。
在实际工作中,我主要使用React和TypeScript进行前端开发。
这次我将汇总在现场被后辈问到的React技术问题。
为了回答问题,我将深入解释而不仅仅采用一问一答的形式。
这篇文章的受众对象
-
- フロントエンジニアを目指している人
-
- React初心者から中級者
- Reactの質問をされた時にうまく言語化できない人
这篇文章的目的是什么?
-
- Reactでよく使われている技術を言語化できるようになる
- 何となくの理解から脱却する
抱歉
-
- 本記事は面接等で聞かれる質問テンプレート集ではありません
- 現場で後輩に聞かれた質問を深ぼって解説をするノリで書いてます
React钩子是什么?
React钩子在官方文档中如下所述。
钩子(hook)是在React 16.8中新增的功能。您可以在不编写类的情况下使用React的功能,如状态等。
在这里,我们将详细讲解React钩子中的useState和useEffect。
请告诉我useState的作用和运行方式。
useState是React内部可用于管理状态的钩子。
在公式文件中的定义如下所示。
返回一个具有状态和用于更新该状态的函数。
setState函数用于更新状态。它接收一个新的状态值,并安排组件进行重新渲染。
对于useState的使用流程,简要来说如下:
-
- useState可以与React内部进行连接,并保持状态。
useState返回当前值以及用于更新值的函数(更新函数)。
当通过更新函数将新值传递给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>
</>
);
};
-
- 在页面加载时进行初始渲染。
-
- 使用useState在React中管理名为value的状态(有初始值)。
-
- 当点击按钮时,新值会传递给更新函数。
-
- 将更新函数传递给React,并重新渲染React组件。
- 屏幕会更新,并显示传递给它的新值。


请给我解释一下 useEffect 函数的作用和工作原理。
useEffect 可在官方文档中以以下方式解释。
通过使用这个钩子,告诉 React 在渲染之后需要进行某些处理。React 会记住你传递的函数(我们称之为“副作用函数”),并在 DOM 更新后调用它。对于这个副作用函数,我们设置了文档标题,但也可以用它来获取数据或调用其他命令式的 API。
当使用 useEffect 时,可以在渲染结果显示在屏幕上后执行 useEffect 内的副作用处理等操作。总的来说,当稍微解释一下,可以使用 useEffect 以实现在渲染结果显示在屏幕上之后执行在 useEffect 内部写入的副作用处理等操作。
在这里,副作用指的是如下的处理事项。
-
- DOMの書き換え操作
-
- サーバー通信(APIコール)
- 変数の書き換え
useEffect函数的第一个参数指定了在渲染时要执行的函数,第二个参数是控制第一个参数函数所依赖的数据。
【在首次渲染时,将“Hello World”输出到日志的处理方法】
export const Parent: React.FC = () => {
useEffect(() => {
console.log("Hello World");
}, []);
return (
<>
<p>テスト</p>
</>
);
};
只要将空数组作为useEffect的第二个参数传递给依赖数据,就只会在首次渲染时执行作为第一个参数编写的函数。
考虑第二个参数依赖数组指定值的情况.
每当count增加时,将执行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>
</>
);
};
在这种情况下,每次点击按钮时,可以确认count参数会增加,并且根据useEffect函数的第一个参数执行相应的处理。
关于状态管理的更新
接下来我们来看一下刚才介绍的useState常见的问题。
要如何更新数组或对象的状态?
由于初学者常常在更新数组和对象的状态时遇到困难,所以下面将详细讲解。
我将根据下面的例子进行解释。
-
- 配列のfruitsというstateを用意する
- ボタンをクリックすると「ぶどう」という文字列が追加され状態を更新する
如果数组的更新不成功的话
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组件没有重新渲染。

当你查看官方文档的useState时,会发现其中的原因。
如果使用当前值进行更新时,React会避免子组件的重新渲染和副作用的执行,并且会通过Object.is比较算法来结束处理。
在更新state的函数中写着,通过将”新的state”放入其中以执行重新渲染,并且在这种情况下,我们直接将值推送到现有的state中,这样就被判定为相同值,从而避免了重新渲染。
const onClick = () => {
// 直接stateに値をpushしても同じ値と検知される
fruits.push("ぶどう");
// 結果、更新関数を使ってもレンダリングが起きない
setFruits(fruits);
};
使用展开语法将现有数组复制并传递给更新函数,从而触发渲染。
如果数组的更新成功
const onClick = () => {
// スプレット構文で既存の値をコピー
const copy = [...fruits];
// コピーに対して値を追加
copy.push("ぶどう");
// 既存のstateとは異なる値(新しい値)が入ってくるのでレンダリングが起きる
setFruits(copy);
};

顺便说一下,这个处理也可以按照下面的方式编写。
const onClick = () => {
setFruits([...fruits, "ぶどう"]);
};
希望您能阅读下面的文章,可以更详细地解释关于这个部分的内容。
关于数组和键的问题
在JSX内循环处理数组时,为什么要指定key呢?
对于在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>
</>
);
};


在官方文档中对于数组和键的解释如下:
Key在React中用于识别哪些元素发生了变化、添加或删除。为了给数组中的项目提供稳定的识别性,每个项目都应该有一个key。最好的方法是选择一个能在兄弟项目之间唯一标识该项目的字符串作为key。在许多情况下,您将使用数据内的ID作为key。
关于为什么要指定一个键,在官方文档中进行了以下解释:
React 在默认情况下,对于 DOM 节点的子节点进行递归处理时,简单地同时从两个子元素列表的开头开始处理,直到找到差异,然后进行更新。
我会使用图示来简明地解释这个部分。
React会检测React元素树的差异并更新DOM。因此,它会根据渲染时的差异在浏览器中进行反映。

如果不提供关键key值,React将视所有React元素作为差异并反映到DOM中。
仅仅是将”橙子”添加到了最前面,却导致了”苹果、香蕉、葡萄”位置的变化,结果所有的都被视为差异。
通过传递 key,即使 React 树的位置发生了变化,也只会识别差异并进行更新,如下所示。

如果不指定密钥,未更改的地方也会被认为是差异,从而降低性能。
在使用key指定值的情况下,推荐使用在索引之上能够唯一标识的值(例如ID)。
请参考下面的文章,了解关于这部分的原因。
关于DOM操作的方法
要操作DOM,该如何做呢?
通过使用useRef,可以进行DOM操作。
在公式文档中,useRef被如下方式描述。
useRef 函数返回一个初始化为传递的参数(initialValue)的可变 ref 对象。返回的对象将在组件的整个存在期间内保持存在。
本质上,useRef就像一个可以在.current属性中保持可重写值的“盒子”。
我会运用具体的例子,并且稍微简化地进行解释。
获取JSX中input的值并存储在变量中。
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です"} />
</>
);
};

基于这个,我将写出以下处理代码。
【在点击按钮时将焦点设置到输入表单的处理】
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时,”useRef”和”useState”的区别是
-
- useaRefは再レンダリングされずにデータを保持する
-
- Refが変更しても再レンダリングされない(中に入っているデータは変わっている)
- useRefはDOM要素にrefを渡す際に用いられる
函数式编程是什么?
我将解释一下在使用React时需要了解的概念——函数式编程。
函数式编程具有以下三个特点。
-
- 状態管理と処理の分離
-
- 純粋関数
- 不変性(immutability)
我会逐一仔细地查看。
状态管理和处理的分离
在函数式编程中,状态管理和处理被分类和管理。
我会具体参考React的代码来确认。
按下+1按钮后,count状态的值会增加1。
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>
</>
);
};
React内部维护了一个名为count的状态,同时将处理函数组件Practice返回的JSX进行分离。
在React中,状态的更新和管理完全由内部进行,与函数组件(Practice)分离了。
纯函数
我将说明函数式编程的第二个特征,即纯函数。
純粹函數具備下列特點:
-
- 引数が同じ値なら返り値は常に同じ値になる
-
- 関数外の状態は参照・更新しない(副作用が発生しない)
- 引数で渡ってきた値を更新しない
首先,我们将验证不满足上述条件的情况。
如果参考或更新了函数外的状态(产生了副作用)。
let count = 0;
export const Child: React.FC = () => {
// 関数外の変数を更新
count++;
return <p>{count}</p>;
};
export const Parent: React.FC = () => {
return (
<>
<Child />
<Child />
</>
);
};
在Child组件内部,对在函数外定义的名为value的变量进行引用和更新。

如果是纯函数的情况下
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} />
</>
);
};

在这种情况下,在Child组件中传递相同的参数(count),我们可以确认得到相同的结果。
在这种情况下,Child组件可以被归类为纯函数。
通过在纯函数中定义React组件,可以防止意外的错误。
不变性 (bù
不可变性(immutability)的特点如下所示。
- 引数で渡された値は変更しない
如果不是不可变性的话
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} />
</>
);
};
在Child组件内部,我们通过props传递的count值进行了更新,这意味着在这种情况下,函数不再是不可变性的。

使用函数式编程的好处
关于函数式编程的总结是,
-
- 状態管理と処理を切り離す
-
- 純粋関数で副作用を排除する
- 不変性
将代码转换为函数式编程方式
-
- 可読性が上がる
-
- 再利用性がある
-
- テストが書きやすい
- モジュール化しやすい
可以获得诸如此类的好处。
最后
请问您觉得如何?
这次我们总结了关于React技术问题的内容。
由于在现场开发时,当被问到一些技术问题时无法很好地表达出来,因此我决定重新整理一下。
因为它只是有关基本内容的说明,所以下一篇文章中我打算写下面这样的内容。
-
- パフォーマンスの最適化
-
- グローバルな状態管理について
- Next.js関連
如果你能读一下我关于前端开发的其他文章,我会很开心的。