如何用React Hook开发带状态的SPA V组件
发表时间:2021-1-11
发布人:葵宇科技
浏览次数:73
把WEB UI分立为一个个带状态的 V,是一个概念革命,因为开发者工作时看到的变量语句函数,就像建筑工人上班时看到的是水泥沙石钢筋,这是一种低层次的作业水平。
把SPA看成一个个V,就像把建筑看一个个独立的生活空间,而不是水泥钢筋。所以学习造V 和Vx,就像学习造生活空间。我们工作处理的是水泥钢筋,变量语句,心中指引我们的,是生活空间,和V逻辑。
EM:把一座大厦建筑比较一个SPA,大厦的「功能空间」比作会话,具体「生活空间」比作会话步骤,是非常相似的。
前一文,我们学习了如何使用React 提供的最基本的 V制作工具—— Component 基类——制作带状态的V组件(主要使用 Component.setState API),本文将介绍React 最新V制作技术——Hook来 完成同一个任务 。本文部分内容摘自《 How To Manage State with Hooks on React Components 》。
Table of Contents
React Hook是什么技术
React Hook 的设计可以概括为一个句话,就是 让开发V组件更轻便,是对之前Component基类工具的一次革新。让开发V组件更轻便 体现在多个方面,本文「实现带本地状态的V」的useSate 和 useReducer 只是其中之一,是最基础的V功能。因为我们还要开发 提供丰富交互功能UI组件,这些复杂UI组件(Vx)需要复杂的状态控制,React提供了useEffect 和 useContext等种类Hook的支持。
为function 组件“勾选”功能
Hook 宣称 让你在不使用class的情况下编写全能的V组件——通过给 function 灵活“勾选”所需要的功能。然而,function本身只是一个无状态的类对象,让它“勾选”了状态功能后就是一个完整的对象object了:
函数本身可是一个只有方法的对象,是一个没状态的特殊对象,引入外功能,就是指将它转为一个特殊的有状态或其它功能的对象,不经过类实例化的轻便对象
这种方式还是很聪明 的。除此外还引入了一定的抽象,便利了开发。
useXXX抽象
技术上,Hook 是React提供的一个非常特殊的库功能,从它的特定使用规则就知道。例如,它必须在固定的地方按一定的顺序(必是function组件语法顶层)上使用。
另外,Hook就是为制作V Vx提供的更方便的 抽象 工具,useXXX 就是在逻辑上:
你的计算任务想要什么东西,我直接给你,你不用管了
# 两种基础的状态 Hook 工具
React Hook 提供四个主要 Hook功能 给开发者“勾选”:
- useState
- useReducer
- useEffect
- useContext
本文实例只介绍了帮助开发一类基本V(独立的私有状态的V)的两个功能Hook。
EM:我喜欢研究React Hook 提供的API,来总结 实际应用 V的种类
useState 与 简单私有状态的V组件
useState 用来开发最基本的V组件,包括简单的无记忆计算(例如加法计算器),和简单的记忆计算(例如开关按键)。
状态就是智能记忆么?
useState 与 有记忆组件是比较好理解的,例如「开关按键」的下一次点击结果取决于上一次的状态;无记忆useState则需要更多的解释,因为好像既然无记忆,还要状态干什么?其实,当用户触发一次交互,它会有一次中间计算,产生一个结果,这个结果就是「状态」,并且一般进行交互输出(产生一UI重渲染),完成一次完整的交互计算。有记忆的组件只是「中间计算参与的数据不单一」,交互功能由多种数据组成而已。
useState并不是真的无记忆,只是无中间记忆,状态在多次重渲染上保持,也是有“记忆”的。
useReducer 与 复杂私有状态的V组件
useState 和 useReducer 都是用来开发独立的私有状态的V,然而,useReducer 针对的具有更繁复业务逻辑的V组件;这繁复性,可以通过「useReducer的API使用」了解得到。
useReducer的API非常类似 Redux的工作模式,增加一个中间 Reducer 进行解耦。交互输入执行的是一个 action 对象(而不是直接调用状态更新逻辑),Reducer再根据action的具体目标进行相应的状态更新。
useEffect 与 松散形式的Vx组件
useEffect是用于制作具有松散形式的Vx组件,就是V的状态不完全是私有的,要与其它组件进行共享协作,使用部分数据从网络上获取。
useContext 与 复合Vx组件树
useContext 用于制作树层次结构Vx组件,方便父子V之间共享数据进行协作,具体略。
# 任务与需求
我们先回顾一下前一文的任务或需求,就是, 我们制作的是什么样的V或SPA程序。
我们制作的程序只有一个产品列表页,上面有一个购物车记录选购的功能,用户可将想买的商品添加上 购物车,当然也可删除错选的商品项,用户能看到每次选购到的商品,以及总消费金额。
EM:这个技术实例用作教学不是特别的优质
useState 与 简单 本地V状态
import React, { useState } from 'react';
const currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
function getTotal(total) {
return total.toLocaleString(undefined, currencyOptions)
}
export default function Product() {
const [cart, setCart] = useState([]); //<--
const [total, setTotal] = useState(0); //<--
function add() {
setCart(['ice cream']);
setTotal(5);
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div className="product"><span role="img" aria-label="ice cream">:icecream:</span></div>
<button onClick={add}>Add</button>
<button
onClick={() => {
setCart([]);
setTotal(0);
}}
>
Remove
</button>
</div>
)
}
这个交互V的功能非常的简单:
- 输入:添加 或 删除 一荐商品记录
- 输出:显示选购商品数目,和总消费价格
技术,Product函数“勾了”两个状态变量:cart and total。
useReducer 与 繁复业务逻辑的本地V状态
import React, { useReducer } from 'react';
import './Product.css';
const currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
function getTotal(cart) {
const total = cart.reduce((totalCost, item) => totalCost + item.price, 0);
return total.toLocaleString(undefined, currencyOptions)
}
...
function cartReducer(state, action) {
switch(action.type) {
case 'add':
return [...state, action.product];
case 'remove':
const productIndex = state.findIndex(item => item.name === action.product.name);
if(productIndex < 0) {
return state;
}
const update = [...state];
update.splice(productIndex, 1)
return update
default:
return state;
}
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
function add(product) {
setCart({ product, type: 'add' });
}
function remove(product) {
setCart({ product, type: 'remove' });
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(cart)}</div>
<div>
{products.map(product => (
<div key={product.name}>
<div className="product">
<span role="img" aria-label={product.name}>{product.emoji}</span>
</div>
<button onClick={() => add(product)}>Add</button>
<button onClick={() => remove(product)}>Remove</button>
</div>
))}
</div>
</div>
)
}
这个交互V的功能其实也很简单,没有充分展现useReducer的特色。
.