Jua*_*her 7 javascript reactjs
我正在完成一项技术挑战,并遇到了一个我以前从未遇到过的场景。
我被要求编写一个购物车,该购物车的 UI 表示基本结帐数据,例如订单总数、购物车中的当前商品等。
其中一项要求是我需要实现一个可以实例化的Checkout 类:
const checkout = new Checkout();
Run Code Online (Sandbox Code Playgroud)
我应该能够从中获取基本信息,例如:
const total = checkout.total();
Run Code Online (Sandbox Code Playgroud)
并通过它添加商品到购物车:
checkout.add(product.id);
Run Code Online (Sandbox Code Playgroud)
是什么让这个问题变得棘手,因为我想不出一种干净的“DRY”方式将其实现到 UI 中。这主要是因为 checkout 类中的任何更新都不会触发任何重新渲染,因为它不是状态的一部分。我通常会为此使用状态变量。
我尝试将状态变量绑定到结账类中的参数,例如:
const [total, setTotal] = useState();
useEffect(()=>{
setTotal(checkout.total)
}, [checkout.total])
Run Code Online (Sandbox Code Playgroud)
但checkout.total只是对方法的引用,所以它永远不会改变,所以我没有得到我想要的绑定。
尝试其他东西我设法组合出一个“解决方案”,但我怀疑这是否是一个好的模式。
我基本上将回调传递给结帐类,每当购物车更新时都会调用该回调。回调是状态变量的设置器,因此:
const [cart, setCart] = useState<string[]>(checkout.cart);
checkout.callback = setCart;
Run Code Online (Sandbox Code Playgroud)
然后在add方法里面:
add(productId) {
// Some code...
this.callback([...this.cart]);
}
Run Code Online (Sandbox Code Playgroud)
这允许每当类的参数发生变化cart时状态变量就会更新。checkout因此,它会在 Cart 组件及其所有具有传递 props 的子组件上触发重新渲染。因此我得到了一个同步的用户界面。
问题是我除了强制重新渲染之外不需要购物车变量。我可以直接从班级获取购物车信息,checkout这就是我所做的。但为了将其反映在 UI 中,我需要更新一些状态变量。它甚至可能是一个计数器,我只是选择cart而不是计数器,以使其更加连贯,我想。
我在这里把事情复杂化了吗?我是否缺少用于此场景的模式?通常如何与实例化的类进行交互并确保 UI 以某种方式根据类的更改进行更新?
编辑(添加缺少的信息):Checkout 类需要实现以下接口:
interface Checkout {
// ...
// Some non relevant properties methods
// ...
add(id: number): this;
}
Run Code Online (Sandbox Code Playgroud)
因此,明确要求该add方法返回this(以便允许函数链接)。
图案的混合
将 OOP 实例与改变内部状态的方法一起使用将阻止观察状态变化 -
const a = new Checkout()
const b = a // b is *same* state
console.log(a.count) // 0
a.add(item)
console.log(a.count) // 1
console.log(a == b) // true
console.log(a.count == b.count) // true
Run Code Online (Sandbox Code Playgroud)
React 是一种面向功能的模式,并使用诸如不变性之类的互补思想。不可变对象方法将创建新数据而不是改变现有状态 -
const a = new Checkout()
const b = a.add(item) // b is *new* state
console.log(a.count) // 0
console.log(b.count) // 1
console.log(a == b) // false
console.log(a.count == b.count) // false
Run Code Online (Sandbox Code Playgroud)
这样,a == b就false有效地发送了重绘该组件的信号。所以我们需要一个不可变的 Checkout类,其中方法返回新状态而不是改变现有状态 -
// Checkout.js
class Checkout {
constructor(items = []) {
this.items = items
}
add(item) {
return new Checkout([...this.items, item]) // new state, no mutation
}
get count() {
return this.items.length // computed state, no mutation
}
get total() {
return this.items.reduce((t, i) => t + i.price, 0) // computed, no mutation
}
}
export default Checkout
Run Code Online (Sandbox Code Playgroud)
演示应用程序
让我们制作一个快速应用程序。您可以单击和按钮将商品添加到购物车。该应用程序将显示正确的count以及total单个项目 -
| 应用程序组件预览 |
|---|
![]() |
现在将类“同步”到组件只是使用普通的 React 模式。直接在组件中使用您的类和方法 -
import Checkout from "./Checkout.js"
import Cart from "./Cart.js"
function App({ products = [] }) {
const [checkout, setCheckout] = React.useState(new Checkout)
const addItem = item => event =>
setCheckout(checkout.add(item))
return <div>
{products.map(p =>
<button key={p.name} onClick={addItem(p)}>{p.name}</button>
)}
<b>{checkout.count} items for {money(checkout.total)}</b>
<Cart checkout={checkout} />
</div>
}
const data =
[{name: "", price: 5}, {name: "", price: 3}]
const money = f =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(f)
Run Code Online (Sandbox Code Playgroud)
一个简单的Cart组件用于JSON.stringify快速可视化每个项目 -
// Cart.js
function Cart({ checkout }) {
return <pre>{JSON.stringify(checkout, null, 2)}</pre>
}
export default Cart
Run Code Online (Sandbox Code Playgroud)
Run下面的演示验证浏览器中的结果 -
class Checkout {
constructor(items = []) {
this.items = items
}
add(item) {
return new Checkout([...this.items, item])
}
get count() {
return this.items.length
}
get total() {
return this.items.reduce((t, i) => t + i.price, 0)
}
}
function App({ products = [] }) {
const [checkout, setCheckout] = React.useState(new Checkout)
const addItem = item => event =>
setCheckout(checkout.add(item))
return <div>
{products.map(p =>
<button key={p.name} onClick={addItem(p)}>{p.name}</button>
)}
<b>{checkout.count} items for {money(checkout.total)}</b>
<Cart checkout={checkout} />
</div>
}
const money = f =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(f)
function Cart({ checkout }) {
return <pre>{JSON.stringify(checkout, null, 2)}</pre>
}
const data = [{name: "", price: 5}, {name: "", price: 3}]
ReactDOM.render(<App products={data} />, document.body)Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>Run Code Online (Sandbox Code Playgroud)
小智 5
嗯,看来您需要共享状态。我想到的第一个解决方案就是使用 Class 组件。您可以在需要时使用强制重新渲染,并无需useEffect黑客即可编写更多自定义逻辑。
在我看来,第二个解决方案更清晰。它使用观察者模式。您需要向您的 Checkout 类添加订阅。所以基本上。
useEffect(() => {
const subscription = (newState) => setState(newState)
const instance = new Checkout()
instance.subcribe(subscription)
return () => instance.unsubcribe(subscription)
}, [setState])
Run Code Online (Sandbox Code Playgroud)
由于 setState 是不可变的,因此该钩子只会运行一次。
Emi*_*son -2
我认为向对象发送回调然后在需要时调用该回调是完全合理的。如果您不想添加任何不必要的数据,请不要:
add(productId) {
// Some code...
this.callback();
}
Run Code Online (Sandbox Code Playgroud)
checkout.callback = () => {
setTotal(checkout.total);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
819 次 |
| 最近记录: |