200字节用于React组件的状态管理
- React钩子 : 这就是管理状态所需要的。
- 〜200 个字节 , 最小+ gz。
- 熟悉的API : 只需照常使用React。
- 最低API : 五分钟就足以弄清楚。
- 用TypeScript编写 以提供自动类型推断。
主要问题是:此软件包是否比Redux更好? 好吧...
- 他少了。 它小40倍。
- 他更快。 隔离组件级性能问题。
- 更容易学习。 无论如何,您都需要能够使用React挂钩和上下文,它们很酷。
- 集成起来更容易。 一次连接一个组件,而不会破坏与其他React库的兼容性。
- 更容易测试。 分别测试reducer浪费时间;简化React组件本身的测试。
- 就键入而言更简单。 编写它是为了最大程度地使用类型推断。
- 他是极简主义者。 这只是React。
代码示例
import React, { useState } from "react" import { createContainer } from "unstated-next" import { render } from "react-dom" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <span>{counter.count}</span> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } render(<App />, document.getElementById("root"))
对未陈述的态度
I (Jamie Kyle- Per。Per 。 )将此库视为Unstated的后继者。 我之所以做“未声明”,是因为我坚信React本身在状态管理方面做得很出色,并且它只缺少一种简单的机制来区分一般状态和逻辑。 因此,我创建了Unstated作为此问题的“最小”解决方案。
随着钩子的出现,React在突出一般状态和逻辑方面变得更好了。 更好的是,从我的角度来看,“未声明”已成为不必要的抽象。
不用说,我相信许多开发人员对如何使用React钩子分离逻辑和应用程序的一般状态一无所知。 这可能仅是由于文档质量不足和社区的惯性所致,但是我相信一个清晰的API能够纠正这一缺陷。
接下来是这个API。 现在,它不再是“用于在React中共享状态和逻辑的最小API”,而是具有“用于理解如何在React中共享状态和逻辑的最小API”。
我真的很喜欢React,我希望React蓬勃发展。 我希望社区放弃像Redux这样的外部库来管理状态,而最终开始全面使用React内置的工具。
如果不是使用Unstated,而是使用React-我将对此表示欢迎。 在您的博客上写它! 在会议上谈论它! 与社区分享您的知识。
下一指南
如果您还不熟悉React钩子,建议您停止阅读并继续阅读
React网站上的优秀文档 。
因此,借助钩子,您可以编写类似于以下组件的内容:
function CounterDisplay() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return ( <div> <button onClick={decrement}>-</button> <p>You clicked {count} times</p> <button onClick={increment}>+</button> </div> ) }
如果组件逻辑需要在多个地方使用,则可以将其取出
放入单独的自定义钩子中:
function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } function CounterDisplay() { let counter = useCounter() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) }
但是,当您需要通用条件而不仅仅是逻辑条件时该怎么办?
上下文在这里很有用:
function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContext(null) function CounterDisplay() { let counter = useContext(Counter) return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { let counter = useCounter() return ( <Counter.Provider value={counter}> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) }
真是太好了! 用这种风格写的人越多越好。
但是,值得增加一些结构和清晰度,以便API可以非常清楚地表达您的意图。
为此,我们添加了createContainer()
函数,以便您可以将自定义钩子视为“容器”,以便不会错误地使用我们简洁明了的API。
import { createContainer } from "unstated-next" function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) } function App() { return ( <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) }
比较我们更改前后的组件文本:
- import { createContext, useContext } from "react" + import { createContainer } from "unstated-next" function useCounter() { ... } - let Counter = createContext(null) + let Counter = createContainer(useCounter) function CounterDisplay() { - let counter = useContext(Counter) + let counter = Counter.useContainer() return ( <div> ... </div> ) } function App() { - let counter = useCounter() return ( - <Counter.Provider value={counter}> + <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) }
如果您使用TypeScript编写(如果没有,我强烈建议您熟悉它),那么您还将获得更好的类型推断。 如果您的自定义钩子是强类型的,则所有其他类型的输出将自动工作。
API
createContainer(useHook)
import { createContainer } from "unstated-next" function useCustomHook() { let [value, setValue] = useState() let onChange = e => setValue(e.currentTarget.value) return { value, onChange } } let Container = createContainer(useCustomHook)
<Container.Provider>
function ParentComponent() { return ( <Container.Provider> <ChildComponent /> </Container.Provider> ) }
Container.useContainer()
function ChildComponent() { let input = Container.useContainer() return <input value={input.value} onChange={input.onChange} /> }
useContainer(Container)
import { useContainer } from "unstated-next" function ChildComponent() { let input = useContainer(Container) return <input value={input.value} onChange={input.onChange} /> }
小费
提示1:合并容器
由于我们正在处理自定义钩子,因此可以在其他钩子中合并容器。
function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment, setCount } } let Counter = createContainer(useCounter) function useResettableCounter() { let counter = Counter.useContainer() let reset = () => counter.setCount(0) return { ...counter, reset } }
提示2:使用小容器
最好将容器做得很小,并明确地专注于特定任务。 如果您需要在容器中使用其他业务逻辑,请在单独的钩子中执行新操作,然后将状态存储在容器中。
function useCount() { return useState(0) } let Count = createContainer(useCount) function useCounter() { let [count, setCount] = Count.useContainer() let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) let reset = () => setCount(0) return { count, decrement, increment, reset } }
提示#3:组件优化
对于unstated-next
,没有单独的“优化”;用于优化React组件的常用方法就足够了。
1)通过拆分组件优化重型子树。
至:
function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <div> <div> <div> <div> </div> </div> </div> </div> </div> ) }
之后:
function ExpensiveComponent() { return ( <div> <div> <div> <div> </div> </div> </div> </div> ) } function CounterDisplay() { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <ExpensiveComponent /> </div> ) }
2)使用useMemo()挂钩优化繁重的操作
至:
function CounterDisplay(props) { let counter = Counter.useContainer()
之后:
function CounterDisplay(props) { let counter = Counter.useContainer()
3)使用React.memo()和useCallback()减少重新渲染的次数
至:
function useCounter() { let [count, setCount] = useState(0) let decrement = () => setCount(count - 1) let increment = () => setCount(count + 1) return { count, decrement, increment } } let Counter = createContainer(useCounter) function CounterDisplay(props) { let counter = Counter.useContainer() return ( <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div> ) }
之后:
function useCounter() { let [count, setCount] = useState(0) let decrement = useCallback(() => setCount(count - 1), [count]) let increment = useCallback(() => setCount(count + 1), [count]) return { count, decrement, increment } } let Counter = createContainer(useCounter) let CounterDisplayInner = React.memo(props => { return ( <div> <button onClick={props.decrement}>-</button> <p>You clicked {props.count} times</p> <button onClick={props.increment}>+</button> </div> ) }) function CounterDisplay(props) { let counter = Counter.useContainer() return <CounterDisplayInner {...counter} /> }
unstated
迁移
由于整个API都是全新的,因此我有意将此库作为单独的软件包发布。 因此,您可以并行安装两个软件包并逐步迁移。
分享您对unstated-next
过渡的印象,因为在接下来的几个月中,我计划根据此信息做两件事:
- 确保
unstated-next
满足unstated
用户的所有需求。 - 确保对于
unstated
有一个迁移到unstated-next
的清晰简洁的过程。
也许我会在旧库或新库中添加一些API,以使开发人员的工作更轻松。 至于unstated-next
,我保证所添加的API将尽可能少,并且我会尽力使该库较小。
将来,我可能会将unstated-next
代码移植回unstated
的新主版本。 unstated-next
仍然可用,因此您可以在同一项目中并行使用unstated@2
和unstated-next
。 然后,当您完成迁移时,可以升级到unstated@3
并删除unstated-next
(当然,更新所有导入文件……应该有足够的搜索和替换空间)。
尽管API发生了巨大变化,但我希望我能为您提供最简单的迁移。 对于可以做得更好的任何评论,我将感到高兴。
参考文献