- 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; ``` ![](https://image.harryrou.wiki/2024-01-15-CleanShot%202024-01-16%20at%2007.44.48%402x.png) ```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; ``` ![](https://image.harryrou.wiki/2024-01-15-CleanShot%202024-01-16%20at%2007.46.48%402x.png) ```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. ![](https://image.harryrou.wiki/2024-01-15-CleanShot%202024-01-15%20at%2008.51.09%402x.png) # 相关内容 # 参考资料 - [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)