由于要做一个使用起来比较舒服的轮子,最近研究了下react的状态管理库,当然,仅限在使用层面,也就是用着舒服的角度来选择到底使用哪个状态管理库。本着在Github上面看看React社区内状态管理库的流行程度和使用程度的层面,来进行选型,然后就有了这篇文章,关于我们最后选择了哪个,文章末尾告知。
选择库的原则如下:
截止目前为止,在Github上面看了一下当前比较流行的几个状态管理库的star数和used by的数量,以及npm上面的周下载量(weekly downloads),这可以从某些方面说明明该框架的受欢迎程度,也有很小的可能性不准确,不过很大程度上,对框架选型是有所帮助的。
| 库名 | github star | github used | npm 周下载量 | 
|---|---|---|---|
| mobx | 23.9k | 83.9k | 671,787 | 
| redux-toolkit | 5.9k | 83.2k | 755,564 | 
| recoil | 13.5k | 83.9k | 95,245 | 
| zustand | 9.4k | 7.2k | 104,682 | 
| rematch | 7.3k | 2.5k | 33,810 | 
| concent | 950 | 65 | 1,263 | 
上面表格中,就是我们接下来要进行挑选的对象,到底中意哪个,还得看看使用起来的时候的姿势,哪个更加舒服。
mobx是一个非常优秀的react状态管理库,这毋容置疑,而且在Github上面,它的使用量也是做到了第一,官方文档地址zh.mobx.js.org。官网上面给的例子是非常简单的,大多数官网也都如此,可是我不需要简单的例子,我需要一个完整的项目的例子,于是参考了github上面的一个项目antd-pro-mobx。mobx需要搭配mobx-react的连接库一起来使用。
按照npm上面的推荐是要使用class + observe函数包裹的方式,最新版本v6:
import React from "react"
import Reactdom from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"
// Model the application state.
class Timer {
    secondsPassed = 0
    constructor() {
        makeAutoObservable(this)
    }
    increase() {
        this.secondsPassed += 1
    }
    reset() {
        this.secondsPassed = 0
    }
}
const myTimer = new Timer()
// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
    <button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
// Update the 'Seconds passed: X' text every second.
setInterval(() => {
    myTimer.increase()
}, 1000)新项目的从头开始,应该不会选择老版本的库区使用,一般会选择稳定的新版本的进行使用,关于typescript方面,看源码是已经在使用typescript来编写了,不过在官网和npm上面并没有看到typescript的蛛丝马迹,可能是还没发版吧。
我们对比我们的原则看下关于mobx:
- 支持typescript -- NO
- 使用函数式编程 -- NO
- 使用舒服 -- OK
- 原子性低问题 -- OK
- 支持esmodule -- OK
关于mobx部分就暂且到这,说的不对的地方欢迎告知,确实是才疏学浅,没怎么用过这么流行的状态管理库。
toolkit,暂且这么叫吧,redux官方状态管理库,cra模板redux(npx create-react-app --template redux)自带状态管理库,cra模板redux-ts(npx create-react-app --template redux-typescript)自带状态管理库.可能这两个模板也导致了toolkit的下载量和使用量非常大。也由于是redux官方库的原因,需要搭配react-redux来搭配使用。这些我们暂且不管,我们看下如何使用,亲测哈,如有使用不当,可以指出。
// index.ts 主入口文件
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootStore } from 'modules/store';
import { fetchInfo } from 'modules/counter';
function App(props: any) {
  const count = useSelector((state: RootStore) =>  state.counter.value);
  const dispatch = useDispatch();
  return (
    <div>
      hello home
      <hr/>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(fetchInfo(2234))}
        >
          fetchInfo
        </button>
        <div>
          <span>{count}</span>
        </div>
    </div>
  );
};
ReactDOM.render(
  <App/>,
  document.getElementById('root'),
);
上面是主入口文件的代码,可以看到这个使用方式还算是比较普遍,符合redux的使用方式。
// modules/store.ts
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import counter from './counter';
import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
const reducer = combineReducers({
  counter,
});
const store = configureStore({
  reducer,
});
export type RootStore = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootStore> = useSelector;
export default store;上面是store主文件的代码,这其实也是官方给出的合理使用方式。
// modules/counter.ts
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
const namespace: string = 'counter';
export interface ICounter {
  value: number;
}
const initialState: ICounter = {
  value: 1,
};
// async 异步函数定义
export const fetchInfo = createAsyncThunk(`${namespace}/fetchInfo`, async (value: number) => {
  await sleep(1000);
  return {
    value: 9000 + value,
  };
});
// 创建带有命名空间的reducer
const counterSlice = createSlice({
  name: namespace,
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchInfo.pending, (state, action) => {
        state.value = 1000;
      })
      .addCase(fetchInfo.fulfilled, (state, {payload}) => {
        state.value = payload.value;
      })
      .addCase(fetchInfo.rejected, (state, {payload}) => {
        state.value = 500;
      });
  },
});
const { reducer } = counterSlice;
export default reducer;上面是在实际module的使用,会产出一个reducer,唯一不优雅的地方在于extraReducers的调用方式采用了串联的方式,不过还可以通过对象的形式进行传递,不过在ts中支持不够友好,如下:
const counterSlice = createSlice({
  name: namespace,
  initialState,
  reducers: {},
  extraReducers: {
    [fetchInfo.pending.type]: (state: Draft<ICounter>, action: PayloadAction<ICounter>) => {
      state.value = 1000;
    },
    [fetchInfo.pending.type]: (state: Draft<ICounter>, { payload }: PayloadAction<ICounter>) => {
      state.value = payload.value;
    },
    [fetchInfo.pending.type]: (state: Draft<ICounter>, action: PayloadAction<ICounter>) => {
      state.value = 500;
    },
  },
});可以看到上面换成了对象的方式,不过在函数里面需要自己去写好类型声明;而串行的方式,typescript已经自动推导出了函数所对应的参数类型。
我们对比我们的原则看下关于toolkit:
- 支持typescript -- OK
- 使用函数式编程 -- OK
- 使用舒服 -- OK,除了builder的链式使用方式
- 原子性低问题 -- OK
- 支持esmodule -- OK
recoil,react官方状态管理库,随着react17而来,官方网址为recoiljs.org,其实透过官方文档,我们可以看到差不多是完全遵循了react hooks的使用方式,不需要搭配任何连接器,可以与react直接无缝连接。不过这其实也导致了原子性比较强,统一的状态管理需要对其进行二次封装,而且工作量不小。在typescript方面,0.3.0开始支持,当前为止最新的版本是0.3.1。例子我就看下官方的例子
import React from 'react';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';
function App() {
  return (
    <RecoilRoot>
      <CharacterCounter />
    </RecoilRoot>
  );
}
const textState = atom({
  key: 'textState', // unique ID (with respect to other atoms/selectors)
  default: '', // default value (aka initial value)
});
function CharacterCounter() {
  return (
    <div>
      <TextInput />
      <CharacterCount />
    </div>
  );
}
function TextInput() {
  const [text, setText] = useRecoilState(textState);
  const onChange = (event) => {
    setText(event.target.value);
  };
  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
}
const charCountState = selector({
  key: 'charCountState', // unique ID (with respect to other atoms/selectors)
  get: ({get}) => {
    const text = get(textState);
    return text.length;
  },
});
function CharacterCount() {
  const count = useRecoilValue(charCountState);
  return <>Character Count: {count}</>;
}由上面,我们可以简单再对比下我们的原则:
- 支持typescript -- OK,虽然力度不是很大
- 使用函数式编程 -- OK
- 使用舒服 -- NO
- 原子性低问题 -- NO
- 支持esmodule -- OK
zustand,这个库,说实话,是第一次看到,不过看了npm上面的例子,这个库还是很好用&很实用的,使用起来很舒服,提供的api不是很多,但是够精简,能够满足需求。没有单独的官网,不过readme写的足够详细,算是个地址吧npm zustand, 我们来看下官网提供的例子:
import React from "react";
import create from "zustand";
import PrismCode from "react-prism";
import "prismjs";
import "prismjs/components/prism-jsx.min";
import "prismjs/themes/prism-okaidia.css";
const sleep = (time = 1000) => new Promise((r) => setTimeout(r, time));
const code = `import create from 'zustand'
const useStore = create(set => ({
  count: 1,
  inc: () => set(state => ({ count: state.count + 1 })),
}))
function Controls() {
  const inc = useStore(state => state.inc)
  return <button onClick={inc}>one up</button>
)
function Counter() {
  const count = useStore(state => state.count)
  return <h1>{count}</h1>  
}`;
const useStore = create((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
  sleep: async () => {
    await sleep(2000);
    set((state) => ({ count: state.count + 30 }));
  }
}));
function Counter() {
  const { count, inc, sleep } = useStore();
  return (
    <div class="counter">
      <span>{count}</span>
      <button onClick={inc}>one up</button>
      <button onClick={sleep}>30 up</button>
    </div>
  );
}
export default function App() {
  return (
    <div class="main">
      <div class="code">
        <div class="code-container">
          <PrismCode className="language-jsx" children={code} />
          <Counter />
        </div>
      </div>
    </div>
  );
}可以看到所有的数据使用的createStore进行包裹,里面可以定义任意类型,可以是count的这样的stats类型,也可以使用函数(包括异步函数),做到了最简单化;另外zustand还提供了一些其他的工具函数和中间件,关于中间件和工具函数等的如何使用,此处就不多说了,可以去npm看看.
由上面,我们可以简单再对比下我们的原则:
- 支持typescript -- OK, 但是官网描述里面的例子比较少
- 使用函数式编程 -- OK
- 使用舒服 -- OK
- 原子性低问题 -- 不高不低,中等,可能需要使用到中间件来包裹,扩展使用
- 支持esmodule -- OK
rematch, 因为有部分项目在使用这个库,所以简单看了下使用。官网上面有很多例子,可以去看看: rematchjs.org. v1的时候是不支持typescript的,使用上有两种方式(对于effects)
// 方式一
effects: {
  fetchInfo: async () => {
    const res = await requestInfo();
    this.setState({
      ...res;
    })
  }
}
// 方式二
effects: (dispatch) => {
  return {
    fetchInfo: async () => {
      const res = await requestInfo();
      dispatch.info.setInfo(res);
    }
  }
}v2的时候是增加了typescript的支持,不过却去掉了上面方式一的使用方式,只保留了第二种。具体例子可以前往rematch typescript 查看。这个使用方式其实与上面的redux-toolkit稍微有点相似,不过好像rematch最近下载量下降了不少。
rematch在模块化封装部分做的很好,能够对所有的状态进行统一管理,然后可以按models进行划分功能,使用起来比较舒服。
由上面以及官网上面的一些例子,我们可以简单再对比下我们的原则:
- 支持typescript -- OK
- 使用函数式编程 -- OK
- 使用舒服 -- OK
- 原子性低问题 -- OK
- 支持esmodule -- OK
concent,另外一种状态管理器的实现方式,在使用上与vue3的setup有很大相似之处。为什么会谈到concent,因为有好几个项目在使用concent,而且表现良好。官方网站concentjs.concent功能非常强大,各种黑魔法,要想使用好concent,会有比较大的学习成本,也就是使用起来可能不会很简单。
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { run, useConcent } from "concent";
const sleep = (time = 1000) => new Promise((r) => setTimeout(r, time));
run({
  counter: {
    state: {
      value: ""
    },
    computed: {},
    lifecycle: {},
    reducer: {
      decrement: async (payload, moduleState) => {
        await sleep(1000);
        return {
          value: moduleState.value - 1
        };
      }
    }
  }
});
const setup = (ctx) => {
  ctx.effect(() => {
    ctx.setState({
      value: 1000
    });
  }, []);
  const increment = () => {
    console.log(1233);
    ctx.setState({
      value: ctx.state.value + 1
    });
  };
  return {
    increment
  };
};
export default function App() {
  const { state, settings, moduleReducer } = useConcent({
    module: "counter",
    setup
  });
  return (
    <div className="App">
      <h1>Hello Counter : {state.value}</h1>
      <div>
        <button onClick={settings.increment}>click increment</button>
        <button onClick={moduleReducer.decrement}>click decrement</button>
      </div>
    </div>
  );
}
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  document.getElementById("root")
);完整的例子和api可以前往官网查看,支持class装饰器的模式,也支持函数setup的方式,也有不使用setup的方式,都能满足;问题就是api较多,学习成本较高。
根据经验和上面的例子,我们可以简单再对比下我们的原则:
- 支持typescript -- OK
- 使用函数式编程 -- OK
- 使用舒服 -- OK,就是学习成本较高
- 原子性低问题 -- OK,支持模块化,状态统一管理
- 支持esmodule -- OK
至此,这几种状态管理库的使用方式和原则对比差不多已经完成。可能有使用不当或者说明错误的地方,欢迎指出。
最终我们考虑了以下几点:
- 支持typescript,使用函数式编程
- 支持模块化,状态统一管理
- 学习成本低,且状态管理库比较流行
- 考虑大家的意见,这很客观,选择一个大家都愿意使用的库
最终选择的是redux官方的toolkit来进行统一的状态管理。
本文结束,感谢阅读,欢迎讨论交流。如有不当之处感谢指出。
本文系转载,阅读原文
https://mp.weixin.qq.com/s/qzL8cuWZsp7ghRmCBHYAGw
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。
受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。
一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。
 
 Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。
React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件
React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。
React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大
 
 这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state
react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)
React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!