初学者参与React教程的成果展示
由React初学者完成的React教程输出。
这是我在Qiita上的首次投稿!
我目前正在努力学习成为一名Web前端工程师,而在实习期间,我们使用Vue和TypeScript进行开发。但是,通过参加Supporters(公司名)的活动,我发现很多企业都在使用React。而且,在我想要工作的企业中,React也是主流技术,因此我开始学习React!
同时,我听说学习一门新语言通常从教程开始(其实并不一定),所以我学习了React教程!
我认为在编程学习中,输出是非常重要的,所以这次我打算输出React教程的内容!
我知道可能会有错误,所以请在评论中指正?♂️
React 是什么?
React是一种声明式、高效且灵活的JavaScript库,用于构建用户界面。React的文件扩展名可以是.jsx或js。
请确认启动代码的内容。
class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
该系统分为三个组件,并且具有Game > Board > Square的父子关系。
逐一确认
class Square extends React.Component {
render() {
return (
<button className="square">
{/* TODO */}
</button>
);
}
}
在Square组件中,只渲染了一个button标签。
class Board extends React.Component {
renderSquare(i) {
return <Square />;
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
在Board组件中定义了一个名为renderSquare的方法,该方法调用了Square组件,并在render方法中向renderSquare方法传递了一个数字作为参数。
此外,还使用const定义了一个玩家。
class Game extends React.Component {
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
使用Game组件调用并渲染Board组件。
使用这个起始代码将显示如下所示

这部分与Vue相似,所以很容易理解!!
通过Props传递数据
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; // valueという名前で引数iをpropsでSquareコンポーネントにわたしている
}
}
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} // this.props.valueでpropsを受け取る
</button>
);
}
}

Vue.js和Props的传递方式有点不同,但我并没有困惑!(直到这里为止)
制作互动组件
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => console.log('click')}> // OnClickメソッドにconsole.log('click)という関数を渡して、buttonタグが押された時にコンソールにclickと表示されるようにする。
{this.props.value}
</button>
);
}
}
在教程中提到,使用箭头函数来定义onclick方法会很好,但为什么会很好我很在意,所以我进行了一些调查,并找到了以下的文章。
为什么在React中要使用箭头函数
阅读后发现,JavaScript的this与此有很大的关系,内容相当深奥,因此我打算另外总结。
这次按照教程上所述,为了避免this的混乱行为,我会使用箭头函数。
然后使用 state 在 Square 组件中,让它记住自己被点击了,并显示一个“X”来填充方格。
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
省略
constructor是什么?super(props)是什么?这样的疑问出现了。
查阅后发现constructor与生命周期密切相关。
生命周期
参考1:
参考2:
这就是组件的人生呢,哈哈。
组件更新 => 用户更新组件管理的数据的过程 => 更新
组件销毁 => 组件变得不再需要并被丢弃的过程 => 卸载

在使用Vue进行开发时,我并没有太意识到生命周期,所以对其理解遇到了困难。
接下来,修改Square的render方法,使其在被点击时显示当前state的值。
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})} // setStateを使うことでstateの値をXに変更する
>
{this.state.value} // stateを表示する
</button>
);
}
}
我之前听说React在状态管理方面很难,但是我感觉我可能会有一些困难理解(但是也期待着?)。
国家的提升
目前的代碼中,Square組件保持了狀態,但是與其由每個Square組件來保持遊戲狀態,不如由父級的Board組件來保持,這樣父級組件可以通過props將信息傳遞給子組件,同時也使子組件與其他兄弟或父級之間實現了常時同步。
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null), //初期 state として 9 個のマス目に対応する 9 個の null 値をセット
};
}
省略
renderSquare(i) {
return <Square value={this.state.squares[i]} />; // Squareがvalueプロパティ('X'、'O'、または空のマス目の場合は null)を受け取るようになる
}
由于Board组件是保存状态的,所以需要从Square组件更改状态。
但是,不能直接从Square直接更改Board的状态。
相反,可以将一个函数从Board传递给Square,这样当方格被点击时,让Square调用该函数。
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)} //handleClickメソッドをSquareに渡す
/>
);
}
class Square extends React.Component {
// constructorを削除
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()} // this.props.onClickでBoardのhandleClickをコールする
>
{this.props.value} // this.props.valueでBoardのstate.squaresを受け取る
</button>
);
}
}
由于未定义handleClick,因此需要定义它。
handleClick(i) {
const squares = this.state.squares.slice(); //sliceメソッドでstate.squaresのコピーを作る
squares[i] = 'X';
this.setState({squares: squares});
}
为了避免直接更改squares,使用.slice()方法创建了一个数组副本。这似乎与不可变性相关。
为什么不可变性很重要?
一般而言,對於變動的資料有兩種不同的方法。第一種方法是直接修改資料的值,對資料進行變異(mutate;改寫)。第二種方法是創建一份經過所需修改的新資料拷貝,並替換原有的舊資料。
带有变异的数据改变
var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}
不涉及变异的数据变化
var player = {score: 1, name: 'Jeff'};
var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}
// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};
使用Object.assign将player的值复制给newPlayer,并通过第三个参数更新score的值。
最终的结果一样,但通过不直接对数据进行改变(即不改写内部数据),可以获得以下一些优点。
・能夠輕鬆實現複雜功能
・檢測變更
・決定React的重新渲染時間
在教程中写着这样,但是我没有太明白,所以我进行了一些调查,并找到了一篇很好的文章!
重要的是不可变性的原因,以及如何使用Immer来轻松实现!
我将大致总结一下这篇文章中的内容。
1. “不变性”是指什么?
就像之前提到的,不可变性指的是状态不会被修改的意思。
在编程中,不可变性意味着不改变状态。
2. 不可变性的重要原因
2-1. 可以阻止无法预料的状态更新。
状态是显示应用程序当前情况的东西。然而,如果在各个地方频繁引用或更改状态,可能会导致无法确定应用程序的当前状态的危险。
因为举例很容易理解,所以我将进行介绍。
const hi = { greeting: 'おはよう' };
// コード1万行
console.log(`${hi.greeting}, みんちゃん`);
如果在这里看到控制台窗口,早上好,可能会出现Minchan。但是,如果有人在一万行代码中更改了hi对象,突然就会出现Goodbye和Minchan等可能性也完全存在。在这种情况下,很难找到是谁更新了hi对象。(而且这是一个错误,所以也不会出现错误)。
如果代码量只是在教程或个人开发时的水平,那么更新状态也会很容易确认。但如果项目规模很大,代码量庞大,找到state的变更也很困难。
2-2. 可以追踪state的变化
当更新对象时,如果生成一个新的”更新后对象”,那么在比较更新前后的对象时,结果会为false。借助这一事实,可以知道对象已经被更新。
这也是React为什么重视不可变性的原因。React会检测状态的变化,并在判断变化前后的对象不同时重新渲染组件。在进行比较时,如果内存地址不同,则结果为false,因此可以快速判断。
虽然我对此还不是很了解,但React是通过虚拟DOM的机制工作的,因此React会检测到构建在其上的虚拟DOM的变化,并将其反映到HTML中。因此,能否追踪state的变化对于React的性能非常重要。
3. 不变性真麻烦
在教程中学习时,我觉得很烦恼,所以当在这篇文章中也被介绍为烦恼时,我松了一口气笑了出来。
听说有一个叫做immer的库,它可以帮助我们轻松地编写遵守不可变性的代码,以解决这种烦人问题,我也想学习关于这个!
我觉得在React中,不可变性非常重要,所以我想要好好学习!学习React时,我深刻意识到我对JavaScript的理解非常浅?
函数组件
有一篇关于函数组件的文章,它是以React教程中的函数组件为基础编写的。我将参考该文章并将其整理成另一篇文章。
处理轮次
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true, // 真偽値で手番を制御する };
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O'; // xIsNextがtrueなら✖️を表示、falseなら◯を表示
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext, // ✖️か○を表示させたらxIsNextを反転させて手番が変わる
});
}
render() {
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); // 手番が誰の番なのかをxIsNextを使って表示
return (
// the rest has not changed
游戏胜利者的决定
定义一个判断胜者的助手方法。
function calculateWinner(squares) {
const lines = [ // このlinesはマス目と対応しており横、縦、斜めに対応している
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]; // [a,b,c]にlinesの配列を入れていく
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]; // 横、縦、斜めに同じマークで埋められていないかを判断して埋まっていた場合、そのマークをreturnする
}
}
return null;
}
在这个方法方面,由于教程几乎没有解释,所以我非常难理解。
render() {
const winner = calculateWinner(this.state.squares); // winnerには○か✖️かnullが入る
let status;
if (winner) {
status = 'Winner: ' + winner; // winnerがnull以外の時にwinnerを返す
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); // winnerがnullの時は次の番をプレイヤーを返す
}
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) { // 勝者が決まっているかマス目が既に埋まっている場合はreturnを返す
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
现在我们可以玩完全的井字游戏了!
添加时光旅行功能
保存着手历史
将过去的 squares 数组保存到名为 history 的另一个数组中。这个 history 数组表示了从开始到结束的棋盘的所有状态,并具有以下结构。
history = [
// Before first move
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// After first move
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// After second move
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
国家的提升,再次发生。
通过将history state放置在Game组件中,可以从子组件Board中移除squares的state。与将Square组件中的“state提升”并移动到Board组件中完全相同,现在我们将把Board中的state提升到顶级的Game组件中。通过这样做,Game组件将完全控制Board的数据,并能够在history中渲染Board的先前移动的数据。
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
};
}
接下来,我们将使Board组件从Game组件中接收squares和onClick属性。由于Board内只有一个点击事件处理程序与多个方块对应,我们将把方块的位置传递给onClick处理程序,以告知哪个方块被点击。按照以下步骤重写Board组件:
• 删除Board的constructor。
• 将renderSquare中的this.state.squares[i]替换为this.props.squares[i]。
• 将renderSquare中的this.handleClick(i)替换为this.props.onClick(i)。
class Board extends React.Component {
// constructorを削除
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.props.squares[i]} // Gameからpropsとして受け取るため変更
onClick={() => this.props.onClick(i)} // Gameからpropsとして受け取るため変更
/>
);
}
以下略
更新游戏组件的render函数来确保在决定和显示游戏状态文本时使用最新的历史记录。
render() {
const history = this.state.history;
const current = history[history.length - 1];
const winner = calculateWinner(current.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
当我看到这段代码时
const history = this.state.history;
const current = history[history.length - 1];
当我无法完全理解这部分内容时,经过查看handliClick方法后,我明白了。
handleClick(i) {
const history = this.state.history;
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
}]),
xIsNext: !this.state.xIsNext,
});
}
以下的部分非常重要
history: history.concat([{
squares: squares,
}]),
在这部分中,通过使用setState方法来更新history,然后通过使用concat方法将新的squares添加到history中(这也是不可变性生效的部分)。
通过这样做,可以创建出如上述所示的history。
history = [
// Before first move
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// After first move
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// After second move
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
我能理解刚刚这段代码的意思
const history = this.state.history;
const current = history[history.length - 1];
将history的状态放入state,将history的方块放入current。
之所以要使用history[history.length – 1],是因为要获取当前state的方块,需要取得history数组的最后一个元素。
例如,如果当前的history如下:
history = [
// 0回目
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// 1回目
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// 2回目(現在)
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
在数组中,squares的索引号是2。
要获取它,可以通过history.length来确认数组的总数。在例子中,可以得到3。
然后减去1,就可以得到最后一个数组项!!
过去的行动显示
const moves = history.map((step, move) => { // stepには要素、moveにはindex番号が入る
const desc = move ? // moveがあるかを判別
'Go to move #' + move :
'Go to game start';
return (
<li>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
选择钥匙。
当React重新渲染列表时,它会检查每个列表项目的key,以确定前一个列表项中是否存在相同的key。如果新列表中包含以前没有的key,React会创建一个新组件。如果前一个列表中的key在新列表中不存在,React会销毁前一个组件。如果两个key匹配,则相应的组件会被移动。key向React提供有关每个组件的唯一性的信息,从而使React能够在重新渲染之间保持状态。如果组件的key发生变化,组件将被销毁并使用新的状态重新创建。
key是一个特殊的属性,由React保留(ref也是类似的高级功能)。在创建元素时,React会提取出key属性,并直接将该键存储在返回的元素中。key看起来像是props的一部分,但不能通过this.props.key进行引用。React会自动使用key来确定哪些子元素应该被更新。组件无法自行检查其key的方法。
在构建动态列表时,强烈推荐分配正确的key。如果没有合适的key,可能需要重新构建数据结构以确保存在此类key。
如果未指定key,React将显示警告并默认使用数组索引作为key。将数组索引用作key会导致在排序、插入/删除项目时出现问题。可以通过显式地传递key={i}来消除警告,但这样做会导致与使用数组索引相同的问题,因此大多数情况下不建议这样做。
key不需要在全局范围内是唯一的,只要在组件和其兄弟之间是唯一的即可。
因此我做了一些研究,因為又遇到了一個困難哈哈。
關於列表和鍵(key)
正如教程中所述,鍵的作用是將重繪過程中的繪製處理浪費降到最低,以防止性能下降。
這裡有一個很好的例子被介紹了。
<ul>
+ <li>lion</li>
<li>cat</li>
<li>dog</li>
<li>bird</li>
</ul>
当有这样的列表时,尝试添加“lion”。
猫是第一位,狗是第二位,鸟是第三位,而狮子是第零位。即使只添加了一个数据,但DOM的兄弟元素数据却全部发生了改变。这意味着React应该重新渲染的目标范围扩大了。
给先前表示的过去操作的按钮列表也设置键。
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}> // keyを設定
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
这也意味着关键点与React的性能有关。对于我这个从未考虑过性能的人来说,这些方面理解起来非常困难。?
时间旅行的实现
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
stepNumber: 0, //stateとしてstepNumberを追加
xIsNext: true,
};
}
jumpTo(step) { // 過去の着手を表示するメソッド
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0, // stepが偶数の時は○の番なのでxIsNextをtrueにする
});
}
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1); //過去の着手に戻った際に戻る前の将来の履歴を消すためにsliceを使っている
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares
}]),
stepNumber: history.length, // stepNumberを更新する
xIsNext: !this.state.xIsNext,
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber]; // stepNumberによって現在選択されている着手をレンダーする。
const winner = calculateWinner(current.squares);
总结
我之前听说React很难,但没想到这么难?
不过只学习了教程,我觉得大概能理解React,并且学习过程非常有趣!
我意识到自己在JavaScript理解和性能方面的知识还有很大的不足。
我也想学习Redux和Hooks(应该也会很难吧〜)
这次写的React教程代码