- Objective: 代码的简洁
- Breadcrumb:
# 概念阐释
## 语义
为了让代码更加简洁与易于维护,我们通常会把React组件进行拆分,分为有状态组件(stateful container)和无状态组件(stateless presentational)。
- **有状态组件(stateful container)**:有状态组件通常作为无状态组件的“容器”,负责组件的功能部分,维护状态和更新、基于 props 进行计算等。并将数据和回调函数作为props传递给无状态组件。有状态组件也是父组件。
- **无状态组件(stateless presentational)**:展示组件的唯一工作是包含 JSX。它应该是**导出的组件**,接收有状态组件的props,并且**不应该渲染自身**,因为无状态组件总是由**容器组件**渲染。无状态组件也是子组件。
```jsx
//无状态组件 - 子组件,接收父组件的props参数
function Presentational(/*...props*/) {
// body of the component
}
export default Presentational;
```
```jsx
//有状态组件 - 父组件
import { Presentational } from 'Presentational.js';
function Container() {
// renders Presentational component
}
```
# 实例
```jsx
// 无状态组件 - 子组件,接收父组件的props参数 favoriteGP, onSelectFavorite, onResetFavorite
// 组件的主要职责是根据接收的props,渲染UI
import React from "react";
function GuineaPigsForm({favoriteGP, onSelectFavorite, onResetFavorite}) {
return (
<div data-testid="guineaPigsForm" id="guineaPigsForm">
<label>Choose Your Favorite Guinea Pig:
<select value={favoriteGP} onChange={onSelectFavorite}>
<option value="0">Alex</option>
<option value="1">Izzy</option>
<option value="2">Brandon</option>
<option value="3">DJ</option>
</select>
</label>
<button onClick={onResetFavorite}>Reset Favorite</button>
</div>
);
}
export default GuineaPigsForm;
```

```jsx
//无状态组件 - 子组件,接收父组件的props参数 src, isFavorite
// 组件的主要职责是根据接收的props,渲染UI
import React from "react";
function GuineaPigsSlideShow({src, isFavorite}) {
return (
<div data-testid="guineaPigsSlideShow" id="guineaPigsSlideShow">
<h1>Cute Guinea Pigs</h1>
<img alt="Guinea Pigs Slideshow" src={src} className={isFavorite? "favorite" : ""}/>
</div>
);
}
export default GuineaPigsSlideShow;
```

```jsx
// 状态组件
import React, { useState, useEffect } from "react";
import { createRoot } from "react-dom/client";
import GuineaPigsSlideShow from "../components/GuineaPigsSlideShow";
import GuineaPigsForm from "../components/GuineaPigsForm";
const GUINEAPATHS = [
"https://content.codecademy.com/courses/React/react_photo-guineapig-1.jpg",
"https://content.codecademy.com/courses/React/react_photo-guineapig-2.jpg",
"https://content.codecademy.com/courses/React/react_photo-guineapig-3.jpg",
"https://content.codecademy.com/courses/React/react_photo-guineapig-4.jpg",
];
function GuineaPigsContainer() {
const [currentGP, setCurrentGP] = useState(0);//设初始值为0
const [favoriteGP, setFavoriteGP] = useState(0);//设初始值为0
const src = GUINEAPATHS[currentGP];// 初始数组为GUINEAPATHS[0]
const favoriteChangeHandler = (event) => {
setFavoriteGP(parseInt(event.target.value)); //当用户在表单select中选择名字时,更新favorite的状态, parsrInt将字符串转换为整数
}
const resetFavoriteHandler = () => {
setFavoriteGP(0); //重置favorite为状态初始值
}
//创建一个图片自动轮播功能,并清除副作用
// 取余运算:假设 `GUINEAPATHS.length` 是 4(意味着有 4 张图片),那么有效的图片索引是 0, 1, 2, 3。
//当 `nextGP` 增加时,它最终会超过 3。比如,如果 `nextGP` 变成了 4,正常情况下这会是一个无效的索引,因为我们只有四张图片。
//但是使用 `nextGP % 4`(如果 `GUINEAPATHS.length` 是 4),当 `nextGP` 是 4 时,结果是 0。当 `nextGP` 是 5 时,结果是 1,以此类推。
//这样一来,不管 `nextGP` 增加到多少,`nextGP % GUINEAPATHS.length` 总是返回一个在 0 到 3 之间的数,保证了图片索引始终是有效的。
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentGP(prevGP => {
const nextGP = prevGP + 1; //从前一个状态更新的函数公式,请使用当前的 `currentGP` 值(我称之为 `prevGP`)来计算新的值,0+1,1+2,2+3,3+4
return nextGP % GUINEAPATHS.length; //
});
}, 5000)
return () => clearInterval(intervalId);
}, []);
return (
<>
<GuineaPigsSlideShow src={src} isFavorite={currentGP === favoriteGP}/> //传递给子组件的props名
<GuineaPigsForm favoriteGP={favoriteGP} onSelectFavorite={favoriteChangeHandler} onResetFavorite={resetFavoriteHandler}/> //传递给子组件的props名
</>
);
}
export default GuineaPigsContainer;
// onSelectFavorite={favoriteChangeHandler} 在子组件中触发用户修改事件onChange={onSelectFavorite}>
// onResetFavorite={resetFavoriteHandler} 在子组件中触发用户修改事件onClick={onResetFavorite}
```
Here are the steps we took:
- Identified that the original component needed to be refactored: it handled calculations/logic and presentation/rendering.
- Created a container component containing all the stateful logic.
- Created a function that calls the state setter method provided by `useState()`.
- Created and exported presentational components containing only JSX.
- Imported the presentational components into the container component.
- Used the presentational components in the return statement of the container component.
- Passed state and functions used to change state as props to the rendered presentational components.

# 相关内容
# 参考资料
- [React programming patterns](https://www.codecademy.com/journeys/full-stack-engineer/paths/fscj-22-front-end-development/tracks/fscj-22-react-part-ii/modules/wdcp-22-stateless-components-from-stateful-components-4e9c9fb5-0711-4b6d-82b5-1719f044b355/lessons/react-programming-patterns/exercises/reactive-programming-patterns-review)