博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
2019,手把手教你用React Hooks解决状态管理(上)
阅读量:7075 次
发布时间:2019-06-28

本文共 5355 字,大约阅读时间需要 17 分钟。

React团队在今年二月发布了React 16.8,想必打开这篇文章的你一定知道这个版本包含了一个令人期待的新特性:Hooks。

作为React 16.8的宠儿Hooks,大家也很关心其带来的变化,其中备受关注的一点就是状态管理,以至于大家很容易可以在各个社区搜到这些问题:

React有了Hooks还需要Redux和Mobx吗?

React Hooks会取代Redux吗?

Redux有Hooks支持吗?

….

看完这篇文章之后也许你还是无法给这些问题一个确切的答案,但是有一个问题的答案是肯定的,就是基于Hooks Api可以解决状态管理问题。

在开始阅读之前,让我们先看一个demo:

(推荐桌面版或者手机浏览器打开,加载需要几十s,需要点耐心⌛️)

这是一个被用烂的demo - TodoList,但又有些不同,就是它用了是全新的Hooks语法和一个没见过的库解决了状态管理的问题(还用了TypeScript

下面,让我们从这个demo出发,把状态管理安排地明明白白

TodoList是如何炼成的

首先:声明全局状态

在用Redux的时候,有两个单词是避不开的:Provider,connect,任你render props还是HOC,总要套个东西上去,要不是装饰器,要不是高阶组件

你常常会看到很多开发者写很多boilerplate less的库,其中最出名的就是Rematch,为的就是少写几行代码。但是就算用了是Context Api,Provider你也是跑不了要写。

看一下Hooks的useState api:

const Counter = () => {    const [state, setState] = useState(0)    // 一堆UI}复制代码

想想之前

@connect( ... )class Counter extends PureComponent {    render() {        const { ... } = this.props // 状态加载中 ...        // 一堆UI    }}复制代码

你可能会想,如果有一天可以这样就好了

const Counter = () => {    const [state, actions] = useStore('Counter')    return }复制代码

路人:

)

路人:“对了对了,让我看一眼你的demo怎么在入口components/App初始化的”

import * as React from "react";import { useStore } from "../models/index";import Header from "./Header";// ...import Filter from "./Filter";const App = () => {  const [state, actions] = useStore("Todo");  const incompletedCount = state.todos.reduce(    (count, todo) => count + !todo.completed,    0  );  return (    
...
);};export default App;复制代码

路人撤回了一条消息。

路人:“但我觉得你肯定在../models/index里做了什么见不得人的事才没放出来,拿来看看”

../models/index

import { Model } from "react-model";import Todo from "./todo";export const { useStore } = Model({ Todo }); // 这里是支持多model的复制代码

路人: "emmmmmm, 那./todo.ts打开"

interface Todo { //  *$#x}type Filter = "All" | "Active" | "Completed";// type  *$#x// type  *$#x// type  *$#xconst defaultTodos = [  // ...];const model: ModelType
= { state: { todos: defaultTodos, filter: "All", id: 9 }, actions: { add: (_, __, title) => { // ... }, edit: (_, __, info) => { // ... }, done: (_, __, id) => { return state => { // ... }; }, allDone: () => { // ... }, undo: (_, __, id) => { // ... }, // ... }};export default model;复制代码

五秒后...

路人:“这都啥,type、interface的,action参数一堆下划线先不说了,这actions里的方法return有object有function,有啥区别吗?”

type,interface都是TypeScript开发者的需要的类型定义,写这些类型最终就是要产出ModelType<State, ActionParams>来表示model的类型,这样调用useStore返回的stateactions就都是有类型的了

而下划线代表那个参数不用,不写下划线会有lint报错,方法里面的参数如果全部用上的话应该是这样的

actions: {    allNeed: (state, actions, title) => { // 当然这里是可以加 async 在前面滴      // state 是当前model的state,      // actions 是当前model里其他的actions      // 你可以这样调用      actions.add('titile')      // return 如果是object的话,会被自动merge在当前的state上,即 return {...state, ...object}      // 如果返回时function的话,会利用immer库在原state上进行操作,生成下一个immutable的state      // 这样在深层嵌套属性上会非常好用      // 返回object        return {            // ...state, 这个库已经帮你做了,不用再写了:)            key: {                ...state.key,                deepkey: {                    ...state.key.deepkey // 路人: 好,你够了,我懂了                    titles: [...state.key.deepkey, title]                }            }        }    	// 返回function    	return state => {     	    state.key.deepkey.push(title) // 路人: 行,有点东西    	}    },}复制代码

路人:“看起来不错,你接着讲吧”

初始完数据之后,各个函数组件就可以用useStore来订阅各自需要的model(s)了,默认情况下订阅同一个model的函数组件在数据更新时都会触发rerender

components/Filter.tsx

const Filter = () => {  const [state, actions] = useStore("Todo");  return (    
    ...
);};复制代码

components/TodoList.tsx

const TodoList = () => {  const [state] = useStore("Todo");  return (      ...  )}复制代码

所以你在点击下面的All, Active, Completed的时候 TodoList 展现的内容也会随之变更。

但是当Todo的数量非常非常大的时候,比如说1W条,这种默认订阅的方式会带来很大的性能开销,一条的数据的更新就要2s,所以useStore提供了第二个参数depActions,这个参数是一个数组,只有数组中的action执行的时候这个组件才会rerender,当然这也要搭配React.memo来一起食用,我写了一个,需要的时候可以参考下 :),简单的优化之后单条修改rerender的时间可以缩减到~200ms

路人B:“人家redux还有中间件 (middleware) 呢,这小库有吗”

这得翻翻库的了

Object.keys(Global.State[modelName].actions).map(    key =>      (updaters[key] = async (params: any, middlewareConfig?: any) => {        const context: Context = {          modelName,          setState,          actionName: key,          next: () => {},          newState: null,          params,          middlewareConfig,          consumerActions,          action: Global.State[modelName].actions[key]        }        await applyMiddlewares(actionMiddlewares, context)      })  )复制代码

这可以看出来 useStore 返回的actions的行为全都是actionMiddlewares决定的。上面提到的组件订阅Store,rerender,以及没提到的redux devtools支持,生产环境下action的try catch等等都是实现的。

如果要对特定的Store做优化或者特殊处理,只需要对actionMiddlewares做操作就可以了。

import { actionMiddlewares, middlewares } from 'react-model'// production模式下替换原来从actions返回获取新的state的中间件为带超时逻辑的中间件if (process.env.NODE_ENV === 'production') {    actionMiddlewares[1] = middlewares.getNewStateWithCache(3000)}复制代码

如果你想自己写中间件也很简单,看下自带的中间件里actions全局tryCatch是怎么做的:

const tryCatch: Middleware<{}> = async (context, restMiddlewares) => {  const { next } = context  await next(restMiddlewares).catch((e: any) => console.log(e))}复制代码

接收参数有当前actions执行时可获取的全部上下文以及后续要执行的中间件,通过context.next就可以将接力棒交给下一个中间件。

路人B:“人家redux支持SSR,Hooks怎么做SSR?”

首先Next.js是完全兼容function的用法的,库的Github上也提供了SSR的demo以及

(此时路人B悄悄离开

哎呀,今天也不早了,基本用法讲到这里也差不多了,而且反正也是上篇,就在这里收笔吧(有没有(下)我也不知道,讲什么我也还没想

最后的最后,如果喜欢这篇文章,欢迎随手点赞分享评论哦,能就更好了?

转载于:https://juejin.im/post/5c6beb965188252f304834b2

你可能感兴趣的文章
Vuejs响应式原理
查看>>
【Nebula系列】C++反射机制:可变参数模板实现C++反射
查看>>
mac 终端 常用命令
查看>>
2016年人工智能产业梳理:一朝引爆,稳步前进(下篇)
查看>>
django 1.8 官方文档翻译:5-1-2 表单API
查看>>
区块链将会怎样颠覆Google、Amazon、Facebook和Apple?
查看>>
VR直播很火,但能取代传统电视直播吗?
查看>>
[转]区块链代码快速学习实践
查看>>
QuickBI助你成为分析师——计算字段功能
查看>>
《王牌特工2》情景再现,Youbionic推出可穿戴式机械手
查看>>
雪城大学信息安全讲义 五、竞态条件
查看>>
干货分享:MySQL之化险为夷的【钻石】抢购风暴
查看>>
量子通信能否跨越“死亡之谷”?2017年市场化的量子通信产品可能产生
查看>>
有序顺序表合并
查看>>
设计模式-观察者模式
查看>>
Spring4-自动装配Beans-按属性名称自动装配
查看>>
精通比特币系列---挖矿与共识
查看>>
to use extended Windows dialogs
查看>>
3A级VR游戏将至?汪丛青力挺G胖正在开发的三款VR游戏
查看>>
Mongodb 3.2 Manual阅读笔记:CH9 存储
查看>>