Fork me on GitHub

Recoil基础--Atoms共享状态

Atoms共享状态

Atoms contain the source of truth for our application state. In our todo-list, the source of truth will be an array of objects, with each object representing a todo item.
Atoms包含了应用的状态资源,在我们的todo-list的例子里,数据来源是一个包含了多个对象的数组,每一个对象就是一个todo item项.
We’ll call our list atom todoListState and create it using the atom() function:
atom()方法创建列表的Atom,并调用todoListState:

1
2
3
4
const todoListState = atom({
key: 'todoListState',
default: [],
});

We give our atom a unique key and set the default value to an empty array. To read the contents of this atom, we can use the useRecoilValue() hook in our TodoList component:
给atom添加一个唯一的标示,设置一个空数组作为默认值。我们可以在组件中使用用useRecoilValue()钩子函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function TodoList() {
const todoList = useRecoilValue(todoListState);

return (
<>
{/* <TodoListStats /> */}
{/* <TodoListFilters /> */}
<TodoItemCreator />

{todoList.map((todoItem) => (
<TodoItem key={todoItem.id} item={todoItem} />
))}
</>
);
}

The commented-out components will be implemented in the sections that follow.
注释部分的组件将会在接下来的部分讲解。
To create new todo items, we need to access a setter function that will update the contents of the todoListState. We can use the useSetRecoilState() hook to get a setter function in our TodoItemCreator component:
创建新的todo items项,需要创建一个新的setter函数来更新todoListState的内容。那么在TodoItemCreator组件中,我们可以通过useSetRecoilState()钩子函数来创建这个setter函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function TodoItemCreator() {
const [inputValue, setInputValue] = useState('');
const setTodoList = useSetRecoilState(todoListState);

const addItem = () => {
setTodoList((oldTodoList) => [
...oldTodoList,
{
id: getId(),
text: inputValue,
isComplete: false,
},
]);
setInputValue('');
};

const onChange = ({target: {value}}) => {
setInputValue(value);
};

return (
<div>
<input type="text" value={inputValue} onChange={onChange} />
<button onClick={addItem}>Add</button>
</div>
);
}

// utility for creating unique Id
let id = 0;
function getId() {
return id++;
}

Notice we use the updater form of the setter function so that we can create a new todo list based on the old todo list.
注意我们使用了setter函数中新的更新器,以至于我们能基于之前的todo list来创建新的todo list.
The TodoItem component will display the value of the todo item while allowing you to change its text and delete the item. We use useRecoilState() to read todoListState and to get a setter function that we use to update the item text, mark it as completed, and delete it:
当更改TodoItem组件的内容或者删除其中一个item的时候,TodoItem组件就展示todo item项的内容。我们通过使用useRecoilState()来读取todoListState并且通过setter函数得到更新的item内容,这意味着更新的完成,并且已经删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function TodoItem({item}) {
const [todoList, setTodoList] = useRecoilState(todoListState);
const index = todoList.findIndex((listItem) => listItem === item);

const editItemText = ({target: {value}}) => {
const newList = replaceItemAtIndex(todoList, index, {
...item,
text: value,
});

setTodoList(newList);
};

const toggleItemCompletion = () => {
const newList = replaceItemAtIndex(todoList, index, {
...item,
isComplete: !item.isComplete,
});

setTodoList(newList);
};

const deleteItem = () => {
const newList = removeItemAtIndex(todoList, index);

setTodoList(newList);
};

return (
<div>
<input type="text" value={item.text} onChange={editItemText} />
<input
type="checkbox"
checked={item.isComplete}
onChange={toggleItemCompletion}
/>
<button onClick={deleteItem}>X</button>
</div>
);
}

function replaceItemAtIndex(arr, index, newValue) {
return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

function removeItemAtIndex(arr, index) {
return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

And with that we’ve got a fully functional todo list! In the next section we’ll see how we can use selectors to take our list to the next level.
这样我们就得到了完整的todolist 组件。在下一个章节,我们将看看selectors是如何将我们的组件提升到一个新的高度的。

Recoil基础--基础介绍

Intro

This section assumes you have installed Recoil and React. See the Getting Started page for how to get started with Recoil and React from scratch. Components in the following sections are assumed to have a <RecoilRoot /> in the parent tree.
这一章节主要是安装Recoil和React,可以查看开始页如何安装Recoil和React,在下面的章节中,根组件中都会使用<RecoilRoot />组件。
In this tutorial, we’ll be building a simple todo-list application. Our app will be able to do the following:
我们会创建一个简单的todo-list的应用。我们的应用会做到以下几点:

  • Add todo items 添加todo项
  • Edit todo items 开发todo项
  • Delete todo items 删除todo项
  • Filter todo items 过滤todo项
  • Display useful stats 展示state
    Along the way, we’ll cover atoms, selectors, atom families, and the hooks exposed by the Recoil API. We’ll also cover optimization
    这样,我们会很好的覆盖到Atoms,selectors,atom全家桶,以及Recoil API 暴露的hooks。

Recoil简介--开始应用

Getting Started

Create React App 创建应用

Recoil is a state management library for React, so you need to have React installed and running to use Recoil. The easiest and recommended way for bootstrapping a React application is to use Create React App:
Recoil是一个为Reactk开发的状态管理库,所以运行Recoil的先安装React,推荐最简单的安装React应用方式就是用如下命令:

1
npx create-react-app my-app

npx is a package runner tool that comes with npm 5.2+ and higher, see instructions for older npm versions.
npx是一个npm5.2或更高版本的运行工具包,点击查看低版本的npm说明

For more ways to install Create React App, see the official documentation.
更多安装应用方式请查看官方文档

Installation 安装

The Recoil package lives in npm. To install the latest stable version, run the following command:
Recoil包已经发布到npm上,安装最新最稳定的版本只要运行以下命令:

1
npm install recoil

Or if you’re using yarn:
或者使用yarn:

1
yarn add recoil

RecoilRoot

Components that use recoil state need RecoilRoot to appear somewhere in the parent tree. A good place to put this is in your root component:
组件中使用Recoil状态需要在父级中使用RecoilRoot,组件中最好的方式就是将RecoilRoot放到根组件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';

function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}

We’ll implement the CharacterCounter component in the following section.
接下来我们会开发一个CharacterCounter的组件。

Atom

An atom represents a piece of state. Atoms can be read from and written to from any component. Components that read the value of an atom are implicitly subscribed to that atom, so any atom updates will result in a re-render of all components subscribed to that atom:
一个Atom就代表一个state,Atom可以被任何一个组件读取,更新。组件读取 Atom 数据将会隐式订阅它,任何更新都会导致订阅它的组件进行重新渲染。

1
2
3
4
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});

Components that need to read from and write to an atom should use useRecoilState() as shown below:
在组件中使用 useRecoilState() 读写 Atom 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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>
);
}

Selector

A selector represents a piece of derived state. Derived state is a transformation of state. You can think of derived state as the output of passing state to a pure function that modifies the given state in some way:
selectore代表派生状态。Derived state是状态的一种转化,在某种意义上你可以认为derived state是纯函数输出的某种state.

1
2
3
4
5
6
7
8
const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({get}) => {
const text = get(textState);

return text.length;
},
});

We can use the useRecoilValue() hook to read the value of charCountState:
我们可以用useRecoilValue()钩子来读取charCountState的值:

1
2
3
4
5
function CharacterCount() {
const count = useRecoilValue(charCountState);

return <>Character Count: {count}</>;
}

Demo

Recoil简介--安装教程

NPM

The Recoil package lives in npm. To install the latest stable version, run the following command:
Recoil包已经发不到npm,可以通过下列命令安装最新的最稳定的版本:

1
npm install recoil

或者是用yarn

1
yarn add recoil

Bundler

Recoil installed via NPM pairs nicely with module bundlers such as Webpack or Rollup.
就像webpack或者Rollup一样,Recoil可以搭配模块坏bundlers通过npm安装。

ES5 support ES5支持

Recoil builds are not transpiled to ES5, and we do not support the use of Recoil with ES5. If you need to support browsers that do not provide ES6 features natively, you can do so by compiling your code with Babel and using preset @babel/preset-env. However, we do not support this and you may run into problems.
Recoil的构建不能转化支持ES5,所以我们并不建议使用ES5。如果你需要兼容不支持ES6的本地浏览器。你也可以通过安装@babel/preset-env插件的方式通过babel转译来实现,但是我们并不支持这么做,这可能会出现问题。

In particular, just like React, Recoil depends on the Map and Set types and other features of ES6. Emulation of these features using polyfills may result in far worse performance.
就像React一样,Recoil依赖于Map或者Set类型的ES6语法,但是如果通过使用polyfills来达到这一目的也许会得到事倍功半的效果。

CDN

Since version 0.0.11, Recoil offers a UMD build that can be directly used in a <script> tag and exposes the symbol Recoil to the global namespace. We recommend linking to a specific version number and build to avoid unexpected breakage from newer versions:
从0.0.11版本开始,Recoil提供了UMD的构建方式,可以直接使用<script>标签,将Recoil暴露到全局。我们推荐引入一个具体版本,以免被收到新版本的影响。

1
<script src="https://cdn.jsdelivr.net/npm/recoil@0.0.11/umd/recoil.production.js"></script>

You can browse all Recoil files on the CDN at jsdelivr.
可以 通过CDN下载所有有关Recoil的资料

ESLint

If you are using eslint-plugin-react-hooks in your project. For example, with an eslint config like this:
如果你在你的项目里使用了eslint-plugin-react-hooks插件,例如,eslint的配置是这样:

1
2
3
4
5
6
7
8
9
10
// previous .eslint config
{
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}

It is recommended to add ‘useRecoilCallback’ to the list of additionalHooks. With this change, ESLint will warn when the dependencies passed to useRecoilCallback() are specified incorrectly and suggests a fix. The format of additionalHooks is a regex string.
推荐在运用hooks的列表中添加useRecoilCallback,有了这个操作,eslint就会在有参数传入useRecoilCallback()函数后执行检查是否正确,并提示修复。hooks的格式是一个正则表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// modified .eslint config
{
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": [
"warn", {
"additionalHooks": "useRecoilCallback"
}
]
}
}

Nightly Builds Nightly构建

We build a package once every day based on the current master branch and publish it as the nightly branch on GitHub. You can use this branch via npm:
我们每天会依赖最新的master分支构建一次,并且以nightly分支发布到Github上,可以通过这个分支使用npm安装:

1
npm install https://github.com/facebookexperimental/Recoil.git#nightly

yarn:

1
yarn add https://github.com/facebookexperimental/Recoil.git#nightly

or add a dependency in package.json and run npm install or yarn:

或者是在package.json中添加依赖,并且通过命令npm install 或者yarn安装:

1
"recoil": "facebookexperimental/Recoil.git#nightly",

Recoil简介--核心概念

OverViews 概览

Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components. Atoms are units of state that components can subscribe to. Selectors transform this state either synchronously or asynchronously.
Recoil可以通过Selector(纯函数)和Atoms(共享状态)来创建组件之间共享数据的数据流图。Atoms是组件可以订阅到的状态单元。Selector可以是同步也可以是异步。

Atoms 共享状态

Atoms are units of state. They’re updateable and subscribable: when an atom is updated, each subscribed component is re-rendered with the new value. They can be created at runtime, too. Atoms can be used in place of React local component state. If the same atom is used from multiple components, all those components share their state.Atoms are created using the atom function:
Atoms就是状态单元,他们是可更新和可订阅的。一但某一个Atom更新,那么订阅他的组件就会得到新值重新渲染。这些Atoms可以在运行时被创建,也可在局部组件中使用。如果同一个Atom被多个组件使用,那么这些组件就会共享这个Atom.Atom可以通过Atom函数来创建:

1
2
3
4
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});

Atoms need a unique key, which is used for debugging, persistence, and for certain advanced APIs that let you see a map of all atoms. It is an error for two atoms to have the same key, so make sure they’re globally unique. Like React component state, they also have a default value.
Atoms需要一个唯一的标示key,用于调试、数据持久化、和确保整个Atom整个系统高阶API正常运行。一旦有俩个Atoms有了相同的key就会报错,所以要确保这个标示全局唯一,就像React组件的状态一样,他们也有自己的默认值。

To read and write an atom from a component, we use a hook called useRecoilState. It’s just like React’s useState, but now the state can be shared between components:
在组件中开发Atom,会用到useRecoilState这个hooks函数。他就像React中的useState一样,但是运用Atom的hooks函数可以让状态在组件之间共享:

1
2
3
4
5
6
7
8
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}

Clicking on the button will increase the font size of the button by one. But now some other component can also use the same font size:
点击按钮就会增加按钮中字体的大小,但是这样在其他组件中用到同样字体大小的时候,其他组件的字体大小也会变化:

1
2
3
4
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}

Selectors 纯函数

A selector is a pure function that accepts atoms or other selectors as input. When these upstream atoms or selectors are updated, the selector function will be re-evaluated. Components can subscribe to selectors just like atoms, and will then be re-rendered when the selectors change.
Selector是一个以Atom和其他的Selector为参数的纯函数。当这些上游的Atoms和Selector更新的时候。该Selecotor就会重新计算。组件也可以像Atom一样订阅Selector.当Selector发生变化的时候,组件也会被重新渲染。
Selectors are used to calculate derived data that is based on state. This lets us avoid redundant state, usually obviating the need for reducers to keep state in sync and valid. Instead, a minimal set of state is stored in atoms, while everything else is efficiently computed as a function of that minimal state. Since selectors keep track of what components need them and what state they depend on, they make this functional approach more efficient.
Selector被用来基于原始State的派生数据的计算。因为不需要使用 reducer 来保证数据的一致性和有效性,所以可以避免冗余数据。而是数据都是通过基于存储在Atom上的原始数据计算得到的。由于 Selector 会追踪使用它们的组件以及它们依赖的数据状态,所以函数式编程会比较高效。
From the point of view of components, selectors and atoms have the same interface and can therefore be substituted for one another.
因为 Seletor 和 Atom 给组件提供相同的方法,所以它们可以相互替代。
Selectors are defined using the selector function:
Selector可以通过Selector函数创建:

1
2
3
4
5
6
7
8
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});

The get property is the function that is to be computed. It can access the value of atoms and other selectors using the get argument passed to it. Whenever it accesses another atom or selector, a dependency relationship is created such that updating the other atom or selector will cause this one to be recomputed.
get属性是一个用于计算的函数。它可以使用入参 get 字段来访问输入的 Atom 和 Selector。当它访问其他 Atom 和 Selector 时,这层依赖关系会保证更新状态的同步。
In this fontSizeLabelState example, the selector has one dependency: the fontSizeState atom. Conceptually, the fontSizeLabelState selector behaves like a pure function that takes a fontSizeState as input and returns a formatted font size label as output.
In this fontSizeLabelState example, the selector has one dependency: the fontSizeState atom. Conceptually, the fontSizeLabelState selector behaves like a pure function that takes a fontSizeLabelState as input and returns a formatted font size label as output.
参考上述 fontSizeLabelState 示例,它依赖于 fontSizeLabelState使用 fontSizeLabelState 作为入参,并返回格式化之后的字号文本。
Selectors can be read using useRecoilValue(), which takes an atom or selector as an argument and returns the corresponding value. We don’t use the useRecoilState() as the fontSizeLabelState selector is not writeable see the selector API reference for more information on writeable selectors):
我们可以通过在 useRecoilValue() 方法中输入 Atom 或者 Selector 来获取对应的数据。这里不用 useRecoilState() 是因为 fontSizeLabelState 是一个不可写 Selector,更多细节参考 Selector

1
2
3
4
5
6
7
8
9
10
11
12
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
<div>Current font size: ${fontSizeLabel}</div>
<button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
}

Clicking on the button now does two things: it increases the font size of the button while also updating the font size label to reflect the current font size.
点击按钮可以看到按钮和文本的字号同时在更新。

Recoil简介

Recoil

A state management library for React。
recoil是react的状态管理库,他具备三大特点。

Minimal and Reactish 简练并保持与 React 一致

Recoil works and thinks like React. Add some to your app and get fast and flexible shared state.
Recoil 的工作方式和原理与 React 完全一致,在React应用中添加Recoil能够快速,灵活的共享状态。

Data-Flow Graph 数据流

Derived data and asynchronous queries are tamed with pure functions and efficient subscriptions.
纯函数和高效订阅为派生数据和异步查询提供支持。

Cross-App Observation 跨应用监听

Implement persistence, routing, time-travel debugging, or undo by observing all state changes across your app, without impairing code-splitting.
不分割代码,通过对全局应用数据对监听,实现数据,路由,调试的持久化,或者是更改成为可能。

Recoil简介--设计初衷

Motivation 初衷

For reasons of compatibility and simplicity, it’s best to use React’s built-in state management capabilities rather than external global state. But React has certain limitations:
秉承简单与兼容性至上的原则,最好的状态管理方式当然是直接使用 React 内置能力而不是外部全部状态。然而 React 确实存在着一些问题:

  • Component state can only be shared by pushing it up to the common ancestor, but this might include a huge tree that then needs to re-render.
    组件状态只能与其祖先组件进行共享,这可能会带来组件树中大量的重绘开销。
  • Context can only store a single value, not an indefinite set of values each with its own consumers.
    Context 只能保存一个特定值而不是与其 Consumer 共享一组不确定的值。
  • Both of these make it difficult to code-split the top of the tree (where the state has to live) from the leaves of the tree (where the state is used).
    以上两点导致组件树顶部组件(状态生产者)与组件树底部组件(状态消费者)之间的代码拆分变得非常困难。

We want to improve this while keeping both the API and the semantics and behavior as Reactish as possible.
我们希望在尽可能保持 React 代码风格和语义化的前提下解决以上问题。

Recoil defines a directed graph orthogonal to but also intrinsic and attached to your React tree. State changes flow from the roots of this graph (which we call atoms) through pure functions (which we call selectors) and into components. With this approach:
Recoil 在组件树中定义了一个正交且内聚的单向图谱。状态变更通过以下方法从图谱的底部(atoms)通过纯函数(selectors)进入组件:

  • We get a boilerplate-free API where shared state has the same simple get/set interface as React local state (yet can be encapsulated with reducers etc. if needed).
    我们提供了一些无依赖的方法,这些方法像 React 局部状态一样暴露相同的 get/set 接口(简单理解为 reducers 之类的概念亦可)。
  • We have the possibility of compatibility with Concurrent Mode and other new React features as they become available.
    我们能够与一些 React 新功能(比如并发模式)兼容。
  • The state definition is incremental and distributed, making code-splitting possible.
    状态定义是可伸缩和分布式的,代码拆分成为可能。
  • State can be replaced with derived data without modifying the components that use it.
    不用修改组件即可派生数据状态。
  • Derived data can move between being synchronous and asynchronous without modifying the components that use it.
    派生数据状态支持同步和异步。
  • We can treat navigation as a first-class concept, even encoding state transitions in links.
    我们把跳转看作一级概念,甚至可以对链接中的状态流转进行编码。
  • It’s easy to persist the entire application state in a way that is backwards-compatible, so persisted states can survive application changes.
    所以可以简单地使用向后兼容的方式来持久化整个应用的状态,应用变更时持久化状态也可以因此得以保留。
  • Copyrights © 2015-2022 Lee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信