blog: 添加React笔记-案例演示
This commit is contained in:
parent
87444e54f8
commit
0858abaed6
|
|
@ -48,6 +48,7 @@ React 元素是[不可变对象](https://en.wikipedia.org/wiki/Immutable_object)
|
|||
|
||||
```jsx
|
||||
function tick() {
|
||||
// element是一个React元素
|
||||
const element = (
|
||||
<div>
|
||||
<h1>Hello, world!</h1>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ function Welcome(props){
|
|||
|
||||
```jsx
|
||||
class Welcome extends React.Component{
|
||||
// render 方法描述你想在屏幕看到的内容
|
||||
render() {
|
||||
return <h1>Hello, {this.props.name}</h1>
|
||||
}
|
||||
|
|
@ -41,6 +42,16 @@ class Welcome extends React.Component{
|
|||
|
||||
|
||||
|
||||
### 函数组件与Class组件在事件处理上的一些区别
|
||||
|
||||
- 使用`class组件`时的事件处理: `onClick={() => this.props.onClick()}`
|
||||
|
||||
- 使用`函数组件`时的事件处理: `onClick={props.onClick}`(注意两侧*都*没有括号)。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 渲染组件(父子组件传值)
|
||||
|
||||
React 元素也可以是用户自定义的组件:
|
||||
|
|
|
|||
|
|
@ -148,6 +148,12 @@ class LoggingButton extends React.Component {
|
|||
|
||||
|
||||
|
||||
### 事件命名规范
|
||||
|
||||
在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 `on[Event]`,将处理事件的监听方法命名为 `handle[Event]` 这样的格式。
|
||||
|
||||
|
||||
|
||||
## 向事件处理程序传递参数
|
||||
|
||||
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 `id` 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ tags:
|
|||
- React
|
||||
---
|
||||
|
||||
# 11.React哲学
|
||||
# 11.React哲学(编写一个复杂组件的原则)
|
||||
|
||||
React 最棒的部分之一是引导我们思考如何构建一个应用。在这篇文档中,我们将会通过 React 构建一个可搜索的产品数据表格来更深刻地领会 React 哲学。
|
||||
|
||||
|
|
@ -21,4 +21,103 @@ React 最棒的部分之一是引导我们思考如何构建一个应用。在
|
|||
|
||||
### 第一步:将设计好的 UI 划分为组件层级
|
||||
|
||||
**首先,你需要在设计稿上用方框圈出每一个组件(包括它们的子组件)**,并且以合适的名称命名。如果你是和设计师一起完成此任务,那么他们可能已经做过类似的工作,所以请和他们进行交流!他们的 Photoshop 的图层名称可能最终就是你编写的 React 组件的名称!
|
||||
**首先,你需要在设计稿上用方框圈出每一个组件(包括它们的子组件)**
|
||||
|
||||

|
||||
|
||||
你会看到我们的应用中包含五个组件。我们已经将每个组件展示的数据标注为了斜体。
|
||||
|
||||
1. **`FilterableProductTable` (橙色):** 是整个示例应用的整体
|
||||
2. **`SearchBar` (蓝色):** 接受所有的*用户输入*
|
||||
3. **`ProductTable` (绿色):** 展示*数据内容*并根据*用户输入*筛选结果
|
||||
4. **`ProductCategoryRow` (天蓝色):** 为每一个*产品类别*展示标题
|
||||
5. **`ProductRow` (红色):** 每一行展示一个*产品*
|
||||
|
||||
|
||||
|
||||
你可能注意到,`ProductTable` 的表头(包含 “Name” 和 “Price” 的那一部分)并未单独成为一个组件。这仅仅是一种偏好选择,如何处理这一问题也一直存在争论。(简单的可以不用单独分一个组件,复杂的作为一个独立的组件就有必要了)
|
||||
|
||||
组件层级:
|
||||
|
||||
- `FilterableProductTable`
|
||||
- `SearchBar`
|
||||
- `ProductTable`
|
||||
- `ProductCategoryRow`
|
||||
- `ProductRow`
|
||||
|
||||
|
||||
|
||||
### 第二步:用 React 创建一个静态版本
|
||||
|
||||
先用已有的数据模型渲染一个不包含交互功能的 UI。最好将渲染 UI 和添加交互这两个过程分开。
|
||||
|
||||
这是因为,编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写太多代码。
|
||||
|
||||
|
||||
|
||||
在构建应用的静态版本时,我们需要创建一些会重用其他组件的组件,然后通过 *props* 传入所需的数据。*props* 是父组件向子组件传递数据的方式。即使你已经熟悉了 *state* 的概念,也**完全不应该使用 state** 构建静态版本。state 代表了随时间会产生变化的数据。
|
||||
|
||||
|
||||
|
||||
你可以自上而下或者自下而上构建应用:自上而下意味着首先编写层级较高的组件(比如 `FilterableProductTable`),自下而上意味着从最基本的组件开始编写(比如 `ProductRow`)。**当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。**
|
||||
|
||||
|
||||
|
||||
React **单向数据流**(也叫*单向绑定*)的思想使得组件模块化,易于快速开发。
|
||||
|
||||
|
||||
|
||||
在 React 中,**有两类“模型”数据:props 和 state**。清楚地理解两者的区别是十分重要的
|
||||
|
||||
|
||||
|
||||
### 第三步:确定 UI state 的最小(且完整)表示
|
||||
|
||||
**只保留应用所需的可变 state 的最小集合,其他数据均由它们计算产生。**
|
||||
|
||||
**可以由state计算产生的数据就没必要另外保存一个state!**
|
||||
|
||||
|
||||
|
||||
对于使用props还是state,通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
|
||||
|
||||
1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
|
||||
2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
|
||||
3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
|
||||
|
||||
> **非state的数据**:
|
||||
>
|
||||
> 1. 由父组件传来的
|
||||
> 2. 不会随时间推移而改变的
|
||||
> 3. 能根据其他stete或props计算而来的
|
||||
|
||||
|
||||
|
||||
### 第四步:确定 state 放置的位置
|
||||
|
||||
我们需要确定哪个组件能够改变这些 state,或者说*拥有*这些 state。
|
||||
|
||||
**注意:React 中的数据流是单向的,并顺着组件层级从上往下传递。**
|
||||
|
||||
|
||||
|
||||
对于应用中的每一个 state:
|
||||
|
||||
- 找到根据这个 state 进行渲染的所有组件。
|
||||
- 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
|
||||
- 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
|
||||
- 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置
|
||||
|
||||
|
||||
|
||||
### 第五步:添加反向数据流
|
||||
|
||||
让**数据反向传递:处于较低层级的表单组件更新较高层级组件的state**
|
||||
|
||||
|
||||
|
||||
实现方式:
|
||||
|
||||
1. 父组件有一个能够修改state改变的回调函数,并传递给子组件
|
||||
|
||||
2. 子组件的input修改值,使用`onChange`事件监听,并执行父组件传入的回调函数。
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
---
|
||||
title: 案例演示
|
||||
date: 2021-03-27 15:04:09
|
||||
permalink: /pages/87146f/
|
||||
categories:
|
||||
- 《React》笔记
|
||||
- 案例演示
|
||||
tags:
|
||||
- React
|
||||
---
|
||||
|
||||
# 案例演示
|
||||
|
||||
本节内容根据官方文档的 [教程](https://zh-hans.reactjs.org/tutorial/tutorial.html) 编写。
|
||||
|
||||
[在线demo](https://codepen.io/gaearon/pen/gWWZgR?editors=0010)
|
||||
|
||||
|
||||
|
||||
## 案例代码
|
||||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
|
||||
// 正方形格子 (没有state时可以使用函数组件)
|
||||
function Square (props) {
|
||||
// return 返回一个React元素
|
||||
return (
|
||||
// <button> 是内置组件,onClick属性是内置组件内规定的点击方法
|
||||
<button
|
||||
className="square"
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.value}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// `棋盘`组件(格子集合)
|
||||
class Board extends React.Component {
|
||||
renderSquare (i) {
|
||||
// 加小括号是防止自动添加分号而破坏结构
|
||||
return (
|
||||
// 向Square组件传递了两个参数 value 和 onClick
|
||||
// 注意:这里的onClick是传参,而不是直接绑定点击事件
|
||||
// 事件命名规范:on[Event]={ this.handle[Event] }
|
||||
<Square
|
||||
value={this.props.squares[i]}
|
||||
onClick={() => this.props.onClick(i)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// render 方法描述你想在屏幕看到的内容
|
||||
render () {
|
||||
return (
|
||||
<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 {
|
||||
constructor(props) {
|
||||
// JavaScript class 继承中的构造函数内必需以super开头
|
||||
super(props)
|
||||
// state 是当前组件私有的
|
||||
this.state = {
|
||||
history: [
|
||||
{
|
||||
squares: Array(9).fill(null)
|
||||
}
|
||||
],
|
||||
stepNumber: 0, // 当前正在查看的历史记录
|
||||
xIsNext: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 处理格子点击(由子组件触发)
|
||||
handleClick (i) {
|
||||
// 这里的slice方法在`回到过去`时,会在副本中把`未来`历史记录丢弃掉,当setState时会抹去原来的`未来`记录。
|
||||
const history = this.state.history.slice(0, this.state.stepNumber + 1);
|
||||
const current = history[history.length - 1]
|
||||
const squares = current.squares.slice() // slice() 取数组副本
|
||||
|
||||
if (calculateWinner(squares) || squares[i]) { // 当有获胜者 或 当前格子已落棋
|
||||
return;
|
||||
}
|
||||
squares[i] = this.state.xIsNext ? 'X' : 'O'
|
||||
this.setState({
|
||||
// concat不会改变原数组,而push会改变原数组
|
||||
history: history.concat([{
|
||||
squares
|
||||
}]),
|
||||
stepNumber: history.length,
|
||||
xIsNext: !this.state.xIsNext
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转历史步骤
|
||||
jumpTo (step) {
|
||||
// 这里并未修改history
|
||||
this.setState({
|
||||
stepNumber: step,
|
||||
xIsNext: (step % 2) === 0
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const history = this.state.history
|
||||
const current = history[this.state.stepNumber]
|
||||
const winner = calculateWinner(current.squares)
|
||||
|
||||
// 历史步骤 (map方法把原数组映射为另一个数组)
|
||||
const moves = history.map((step, move) => {
|
||||
const desc = move ?
|
||||
'Go to move #' + move :
|
||||
'Go to game start';
|
||||
return (
|
||||
<li key={move}>
|
||||
<button onClick={() => this.jumpTo(move)}>{desc}</button>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
let status
|
||||
if (winner) {
|
||||
status = '获胜者: ' + winner;
|
||||
} else {
|
||||
status = '下一步玩家: ' + (this.state.xIsNext ? 'X' : 'O');
|
||||
}
|
||||
|
||||
// 注意:这里的onClick是传参,而不是直接绑定点击事件
|
||||
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>{moves}</ol>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Game />,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
||||
// 计算获胜者
|
||||
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;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 为什么不可变性在 React 中非常重要
|
||||
|
||||
案例中,在修改`state`时并非直接修改对象属性内的某个直,而是先使用`.slice()` 方法创建了数组的一个副本。然后在通过`setState()`方法,替换掉整个数组。
|
||||
|
||||
|
||||
|
||||
一般来说,有两种改变数据的方式。第一种方式是直接*修改*变量的值,第二种方式是使用新的一份数据替换旧数据。
|
||||
|
||||
### 直接修改数据
|
||||
|
||||
```js
|
||||
var player = {score: 1, name: 'Jeff'};
|
||||
player.score = 2;
|
||||
// player 修改后的值为 {score: 2, name: 'Jeff'}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 新数据替换旧数据
|
||||
|
||||
```js
|
||||
var player = {score: 1, name: 'Jeff'};
|
||||
|
||||
var newPlayer = Object.assign({}, player, {score: 2});
|
||||
// player 的值没有改变, 但是 newPlayer 的值是 {score: 2, name: 'Jeff'}
|
||||
|
||||
// 使用对象展开语法,就可以写成:
|
||||
// var newPlayer = {...player, score: 2};
|
||||
```
|
||||
|
||||
不直接修改(或改变底层数据)这种方式和前一种方式的结果是一样的,这种方式有以下几点好处:
|
||||
|
||||
|
||||
|
||||
### 简化复杂的功能
|
||||
|
||||
**不可变性使得复杂的特性更容易实现**。在后面的章节里,我们会实现一种叫做“时间旅行”的功能。“时间旅行”可以使我们回顾井字棋的历史步骤,并且可以“跳回”之前的步骤。这个功能并不是只有游戏才会用到——撤销和恢复功能在开发中是一个很常见的需求。**不直接在数据上修改可以让我们追溯并复用游戏的历史记录**。
|
||||
|
||||
|
||||
|
||||
### 跟踪数据的改变
|
||||
|
||||
如果直接修改数据,那么就很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次。
|
||||
|
||||
**跟踪不可变数据的变化相对来说就容易多了。**如果发现对象变成了一个新对象,那么我们就可以说对象发生改变了。
|
||||
|
||||
####
|
||||
|
||||
### 确定在 React 中何时重新渲染
|
||||
|
||||
不可变性最主要的优势在于它可以帮助我们在 React 中创建 *pure components*。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。
|
||||
|
||||
查阅[性能优化](https://zh-hans.reactjs.org/docs/optimizing-performance.html#examples)章节,以了解更多有关 `shouldComponentUpdate()` 函数及如何构建 *pure components* 的内容。
|
||||
|
||||
###
|
||||
Loading…
Reference in New Issue