参考链接:
React 哲学官方文档
刚使用React的同学有一个通病:拿到UI图后,没有任何思考和设计就去写,想到什么写什么,不合理的代码会出现很多bug,没错说的就是我自己~~~,那么我们就需要学习一下React哲学,看看别人是如何思考构建页面的。
React哲学 会引导我们思考如何构建一个应用 , 倡导我们把代码写得更加简洁清晰,更具模块化,这一点在写大型的项目尤为重要,在写代码之前就把大致的结构和涉及的数据结构设计好,会减少 Bug 的产生,减少重构的时间,减少维护的成本。
准备阶段
在我们写代码之前 ,一定需要UI产品设计图 和后端接口数据
UI产品设计图
后端接口数据
[
{category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
{category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
{category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
{category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
{category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
{category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];
第一步:通过UI产品设计图划分组件层级
理清UI产品设计图的需求:
- 一个展示商品的列表
- 对商品进行关键字搜索,点击复选框展示商品现货,
- 商品列表包含商品名和价格,商品支持分类显示,其中告罄的商品名为红色显示
划分组件层级:
- 在设计稿上用方框圈出每一个组件(包括它们的子组件),并且以合适的名称命名(很关键)
- 将组件当作JS函数考虑,一个组件原则上只能负责一个功能,如果组件功能较多,可以考虑将它拆分成更小的组件
- UI应与数据模型一一对应 , 若将 UI 分离为组件,其中每个组件需与数据模型的某部分匹配。
不同的颜色划分成不同的组件,可以分成五部分:
FilterableProductTable (橙色): 是整个示例应用的整体SearchBar (蓝色):接受所有的用户输如ProductTable (绿色): 展示数据内容并根据用户输入筛选结果ProductCategoryRow (天蓝色): 为每一个产品类别展示标题ProductRow (红色): 每一行展示一个产品
组件的层级划分:
- FilterableProductTable
- SearchBar
- ProductTable
- ProductCategoryRow
- ProductRow
第二步:用React构建静态页面
编写应用 :将编写静态页面和添加交互这两过程分开, 这是因为编写静态页面时需要大量代码,而添加交互时则要考虑大量细节, 将这两个过程分开进行更为合适 ,整个思路也比较清晰。
通过复用编写的组件,使用 props 来进行数据的传递,父组件把数据进行层层的传递,在这个过程中先不使用 state ,因为 state 表示的是会随着时间变化而变化的,所以在交互的过程中使用 。
props和state的区别:
props 是传递给组件的(类似于函数的形参),是父组件向子组件传递数据的方式state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量), 代表了随时间会产生变化的数据,应当仅在实现交互时使用- 组件可以选择把它的 state 作为 props 向下传递到它的子组件中
构建应用的方法:
自上而下的方法 :先写层级最高的组件,如FilterableProductTable 组件,这种比较适合简单的应用自下而上的方法 :先写层级最低的组件,如 ProductRow组件,这种方法比较适合大型的应用构建
静态页面代码示例
const PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];
const FilterableProductTable = () => (
<div>
<SearchBar />
<ProductTable products={PRODUCTS} />
</div>
);
const SearchBar = () => (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" /> Only show products in stock
</p>
</form>
);
const ProductTable = ({ products }) => {
const rows = [];
let lastCategory = null;
products.forEach((product) => {
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category}
/>
);
rows.push(<ProductRow product={product} key={product.name} />);
}
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
};
const ProductCategoryRow = ({ category }) => (
<tr>
<td colSpan="2">{category}</td>
</tr>
);
const ProductRow = ({ product }) => {
const name = product.stocked ? (
product.name
) : (
<span style={{ color: "red" }}>{product.name}</span>
);
return (
<tr>
<td>{name}</td>
<td>{product.price}</td>
</tr>
);
};
第三步:确定 state 的最小且完整的集合
想要使你的 UI 具备交互功能,需要有触发基础数据改变的能力 ,React 通过实现 state 来完成这个任务。
state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量), 代表了随时间会产生变化的数据 ,应当仅在实现交互时使用 。
看一下当前应用有哪些数据:
- 商品的原始数据
- 用户的搜索数据
- 复选框是否选中的值
- 经过筛选后的数据
通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
- 数据是否能通过 props 来传递
- 是否会通过时间而产生改变
- 是否可以通过其他 state和props计算得到
最后确认,原始数据可以通过 props传递,用户搜索的数据和复选框的值可以作为 state ,筛选后的数据可以通过原始数据和用户搜索数据以及复选框数据计算得来。
最后 state是:
第四步:确定 state 放置的位置
确定 state 放置的位置:
- React是
自上而下的单的数据流 ,我们应把 state写在这些 state 的组件的共同父组件中 - 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。
第五步:添加反向数据流
当我们要通过层级较低的组件改变层级较高的组件,就需要通过反向数据流 的方式。
React中的反向数据流是需要高层级组件通过 props把改变 state的方法 (回调函数) 传递给低层级组件 ,低层次组件state改变后将值传给回调函数。
数据驱动UI : 组件化的框架就是数据驱动UI的 ,操作由React完成,你只需关注数据, 因为数据和UI之间建立了联系,通过操作这个数据,UI就能够自动进行变化。
const FilterableProductTable = () => {
const [filterText, setFilterText] = React.useState("");
const [inStockOnly, setInStockOnly] = React.useState(false);
return (
<div>
<SearchBar
filterText={filterText}
setFilterText={setFilterText}
inStockOnly={inStockOnly}
setInStockOnly={setInStockOnly}
/>
<ProductTable
products={PRODUCTS}
inStockOnly={inStockOnly}
filterText={filterText}
/>
</div>
);
};
const SearchBar = ({
filterText,
setFilterText,
inStockOnly,
setInStockOnly,
}) => {
const handleProductsSearch = (value) => {
setFilterText(value);
};
const handleStockCheck = (value) => {
setInStockOnly(value);
};
return (
<form>
<input
type="text"
placeholder="Search..."
value={filterText}
onChange={handleProductsSearch}
/>
<p>
<input
type="checkbox"
value={inStockOnly}
onChange={handleStockCheck}
/>{" "}
Only show products in stock
</p>
</form>
);
};
const ProductTable = ({ products, inStockOnly, filterText }) => {
const rows = [];
let lastCategory = null;
products.forEach((product) => {
if (product.name.indexOf(filterText) === -1) {
return;
}
if (inStockOnly && !product.stocked) {
return;
}
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category}
/>
);
rows.push(<ProductRow product={product} key={product.name} />);
}
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
};
最后:需要明白组件拆分与封装
组件拆分 :组件拆分需要考虑布局和交互。需要整体考虑布局和交互,把它的特性分析完毕后,再去考虑拆分。 理解组件化的时候,分析清楚所有的特性,布局特性、交互特性、数据特性,分析完整之后你再去设计,否则会导致你思考出来的拆分可能有问题。拆分有问题,就会导致你实现起来很困难。
组件封装 : 把重复出现多的地方进行封装,把共性(一样)的东西封装成组件或者函数,把差异性(不一样)的东西设计成参数。然后差异性的参数与函数里的共性部分组合起来,完成组件封装。
组件封装的作用 :
- 简化代码提高可读性
- 通过共性和差异性封装,提高代码复用
- 维护时逻辑清晰,修复时快速定位问题源头。
|