教程网址:https://react.dev/learn/tutorial-tic-tac-toe#setup-for-the-tutorial
-在 React 中,组件是一段可重用的代码,代表用户界面的一部分。组件用于渲染、管理和更新应用程序中的 UI 元素。
-给函数加上export关键字,使此函数可在此文件外部访问。
-给函数加上default关键字,告诉使用该文件的代码的其他文件(如index.js),该函数是此文件的主要函数/top-level component。
-JSX 元素是 JavaScript 代码和 HTML 标签的组合。
-index.js是组件与Web浏览器之间的桥梁,导入React包,导入React DOM包,导入style.css,导入App.js,并且把所有的东西组合为final product,注入到index.html。
-React组件需要返回单个 JSX 元素,而不是多个相邻的 JSX 元素。使用Fragments(<>
和</>
)包装多个相邻的 JSX 元素来返回。
-定义Square组件
function Square() { return <button className="square">1</button>; }
-定义了一个名为
Square
的组件,该组件渲染一个带有类名“square”的按钮元素。-一旦你定义了一个组件(比如
Square
),你可以在任何其他组件内部通过JSX语法<Square />
来引用它。这就像是在HTML中使用自定义标签一样,React会处理这些标签,每次使用<Square />
时,React都会创建一个新的Square
实例,并根据Square
函数中定义的规则渲染相应的DOM元素。-代码重用、结构清晰、封装性好
-Props:是父组件向子组件传递数据的方式。可以把props想象成一个组件的配置参数,它们是单向从父组件传递到子组件的。子组件接收这些参数后可以使用它们进行渲染或其他逻辑处理。
function Square({ value }) { return <button className="square">{value}</button>; }
<div className="board-row"> <Square value="1" /> <Square value="2" /> <Square value="3" /> </div>
通过props生成具备不同value的square
-为格子添加点击事件:
function Square({ value }) { function handleClick() { console.log("clicked!"); } return ( <button className="square" onClick={handleClick}> {value} </button> );
-直接从传入的props对象中提取
value
属性-
onClick={handleClick}
:是React特有的,用来在按钮被点击时调用handleClick
函数。这里的{}
是JavaScript表达式的标识,允许在JSX中嵌入JavaScript代码。
-useState的使用:
import { useState } from "react"; function Square({}) { const [value, setValue] = useState(null); function handleClick() { setValue("X"); } return ( <button className="square" onClick={handleClick}> {value} </button> ); }
- 使用
import { useState } from 'react';
从React库中引入useState
函数。这是一个React Hook,允许在函数组件中添加状态。- 对于每个
Square
组件的实例,React调用useState(null)
。这是组件函数首次执行的一部分。在这一步,useState
设置初始状态null
并返回两个值:当前状态value
和更新这个状态的函数setValue
。
- useState(null)
调用了useState
钩子,并将初始状态设置为null
。这意味着变量value
在组件第一次渲染时的值将是null
。
- useState
返回一个包含两个元素的数组:当前状态的值和一个更新这个状态值的函数。通过数组解构,我们得到value
(当前的状态值)和setValue
(一个用来更新value
的函数)。- 调用setValue来更新value值,触发组件重新渲染,反应新的状态(React的核心特性之一:基于状态的UI自动更新。)
- 每个square都有自己的state,每个suqare保存的value也互相独立
- React的纤程(Fiber)架构
React内部使用纤程(Fiber)架构来管理组件树和状态。每个组件实例对应一个Fiber节点,这个节点包含了组件的状态、props、回调函数等信息。这使得React可以非常精确地跟踪和更新组件状态,即使组件是动态生成的并且数量众多。
- React DevTools 检查 React 组件的 props 和状态。
- Lifting state up:
相比让board一个个去询问每个square的state,不如让Board组件保存整个游戏的state,然后通过让board传递一个prop来告诉每个square该展示什么内容。
将状态提升到父组件:要从多个子组件收集数据,或让两个子组件相互通信,需要在其父组件中声明共享状态(子组件不再需要状态)。父组件可以通过 props 将状态和修改状态的函数如setState传递回子组件,使子组件彼此和父组件之间同步。
使用从父组件传递下来的函数在子组件中更新状态,这会反过来更新父组件的状态,并通过新的props更新所有相关的子组件。
import { useState } from "react"; function Square({ value, onSquareClick }) { return ( <button className="square" onClick={onSquareClick}> {value} </button> ); } export default function Board() { const [squares, setSquares] = useState(Array(9).fill(null)); function handleClick(i) { const nextSquares = squares.slice(); nextSquares[i] = "X"; setSquares(nextSquares); } return ( <> <div className="board-row"> <Square value={squares[0]} onSquareClick={handleClick(0)} /> ······
-
Board
组件管理所有方格的状态,并通过props向每个Square
组件传递状态和事件处理函数。所有Square共享来自同一个父组件的状态。-闭包closure:JavaScript 支持闭包,这意味着内部函数(例如
handleClick
)可以访问外部函数(例如Board
)中定义的变量和函数。该handleClick
函数可以读取squares
状态并调用setSquares
方法,因为它们都是在Board
函数内部定义的。- 不可变性原则immutability:避免直接修改状态数据,创建一个状态的副本,修改这个副本,然后用新的副本来更新状态。在事件处理函数中,用浅拷贝创建数组副本,再更新数组。
1.避免直接修改状态:如
squares[i] = "X"
,React可能无法检测到状态已经变化,因为对象或数组的引用没有变化。2.确保组件重新渲染:通过使用如
.slice()
方法创建数组的副本,然后修改这个副本,可以保证数组的引用是全新的。这样当新数组通过setSquares(nextSquares)
设置为新状态时,React 会检测到状态对象的引用变化,并触发组件的重新渲染。3.维护历史状态:需要管理历史状态或进行撤销和重做操作(time travel)的应用中,保持原始状态的不变是很重要的。通过对原始状态进行浅拷贝而非直接修改,可以确保原始状态不被篡改。
4.简化数据的比较:
(1)使用浅拷贝创建新对象或数组:当更新对象或数组时,不要直接修改它。应该创建一个副本,并在这个副本上做修改。
(2)利用
React.memo
和shouldComponentUpdate:
对于函数组件,可以用
React.memo
来包裹组件,它会对组件的props进行浅比较,如果props没有变化,则不会重新渲染组件。对于类组件,可以通过实现
shouldComponentUpdate
方法来决定组件是否应该更新。在这个方法中,你可以通过比较新旧props或state来决定是否需要渲染。(也可以使用PureComponen--
是React中的一个类组件,它自动实现了shouldComponentUpdate
,通过浅比较props和state来避免不必要的渲染。)-浅拷贝:数组中的元素(这里是字符串或
null
)被复制到一个新的数组对象中,但元素本身是基本类型,不涉及深拷贝的问题。修改这个新数组的某个元素并不会影响原始数组。- Too many re-renders错误:传递了
handleClick(0)
作为Square
组件的onSquareClick
prop 的值。handleClick(0)
是一个函数调用,而不是一个函数引用。这意味着每当Board
组件渲染时,handleClick(0)
都会被调用,导致状态更新并再次触发渲染。由于每次渲染都会触发另一个渲染,形成无限循环。- 解决方法: 向
Square
组件传递一个函数,而不是函数调用的结果。这可以通过将handleClick
函数包装在另一个函数中来实现<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
- 这样单击
Square
,子Square
组件现在会要求父Board
组件更新棋盘的状态。当Board
的状态发生变化时,Board
组件和每个子组件Square
都会自动重新渲染。
-React命名惯例:
使用onSomething
的形式来命名表示事件的props。这里的“Something”通常是事件的名称,比如onClick
、onSubmit
等。(对于内置的DOM元素,如<button>
,其onClick
属性具有特殊的意义,因为它直接关联到React的事件处理系统。这意味着当用户点击按钮时,React知道如何捕捉这个事件并执行相应的处理函数。)
使用handleSomething
的形式来命名处理这些事件的函数。这里的“Something”通常与事件相关联,例如handleClick
、handleSubmit
等。