react 官网教程跟学
我对于前端开发的感受是,实际工程和教程实例代码的差距很大,还有很容易陷在改局部需求,最终变成查文档和写局部 JS 代码的工具人,而失去对项目的总体认识。因为前端一般项目庞大,引入库众多,加上每个库都有自己独特的写法,写多了,就只知其一,不知其二了。
综上,为了建立对 react 项目的整体认知,我就第 N 次过一遍官网的教程,同时记录那些概念和项目结构。
为了方便,我用到的开发环境为 codesanbox 提供的在线 react 环境。即时渲染,非常方便。
项目结构
public
有一个index.html
, 是 react 项目的唯一网页入口,也就是常说的单页面应用(SPA)。里面有一个 root 节点。src/index.js
就是网页的根脚本,获取 root dom 作为组件树挂载的地方。src/App.js
是一个范例组件,通过export default
导出,index.js
导入并渲染。
组件
react 就是由组件组成的。组件是一个返回标签的 JS 函数。区分 react 组件和 html 标签的方法是,react 组件以大写字母开头,遵守 camel 命名。
组件内定义的标签是用 JSX 语法写成的,有别于 html 语法。JSX 最大的特点就是在标签里有 js 逻辑。JSX 有很多语法规则,我这里记录一下我认为比较重要的部分:
- return 之后一般会跟一个括号,把 JSX 标签包起来。
- 用大括号
{}
来插入 JavaScript 表达式,而且还接受函数、对象。
渲染列表
渲染列表两个要素:map 函数和 li 标签。
const listItems = products.map((product) => (
<li key={product.id}>{product.title}</li>
));
return <ul>{listItems}</ul>;
注意,li 标签一定要有一个key
属性,用于 vdom 渲染。
useState / Prop
useState 是一个钩子函数,管理变量用的。Prop 是指传递给组件的参数。
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update together</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
function MyButton({ count, onClick }) {
return <button onClick={onClick}>Clicked {count} times</button>;
}
count
和onClick
就是 props,它们被MyButton
组件用一个花括号包起来一起接收。
其实,传递函 数类型的 prop 时,应该用匿名函数的形式:
<MyButton
count={count}
onClick={() => {
handleClick();
}}
/>
这样,如果 handleClick 需要传入参数,也可以正常执行。
触发时机
import { useState } from "react";
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
function calculateWinner(squares) {
const 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];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
这是官网井字棋游戏的部分代码,我读到这里产生一个疑问,为什么calculateWinner
会在 status 那里将最终赢家给出来。我想的是 winner 这一段只会在初始化执行一次。后面我明白,每当玩家点击方格并触发 handleClick(i)
函数时,squares
的值会更新,然后 setSquares(nextSquares)
会触发组件的重新渲染,接着 calculateWinner(squares)
会再次被调用,检查当前棋盘是否存在获胜者。由于 React 中状态更新后会触发组件重新渲染,calculateWinner
每次都会在组件渲染时被调用以检查当前的胜利情况。
状态提升
因为 reactjs 是基于单向数据流的,组件的状态通过 props 向下传递,从父组件传递到子组件。子组件不能直接修改从父组件传来的数据,而是通过触发父组件的回调函数来更新父组件的状态,父组件状态变化后再将新的状态传给子组件。所以,状态需要被提升到父组件中进行管理。