Fork me on GitHub

手动实现Promise.all()方法

手动实现Promise.all()函数是面试中常做的现场coding考察,那么如何实现呢?我们先来看看Promise.all()的用法。

1
2
3
Promise.all([p1, p2, p3]).then(result => {
console.log(result)
})

Promise.all()方法接受一个数组为参数,数组中是promise,如果数组中的promise都是resolve状态,那么Promise.all()正常返回resolve,返回的数据为一个数组,就是参数中每个promise的结果组成的数组。如果promise.all()中任何一个是reject,那么promise.all()直接reject。
所以Promise.all()的特点可以总结为:

  • 1、接收一个 Promise 实例的数组或具有 Iterator 接口的对象,

  • 2、如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象

  • 3、如果全部成功,状态变为 resolved,返回值将组成一个数组传给回调

  • 4、只要有一个失败,状态就变为 rejected,返回值将直接传递给回调all() 的返回值也是新的 Promise 对象

来看具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//  假设我们已经实现了_Promise
_Promise.prototype.all = (promiseList) => {
return new _Promise((resolve, reject) => {
if (!Array.isArray(promiseList)) {
reject(new TypeError('参数错误!'))
}
let count = 0
let valueList = new Array(promiseList.length)
promiseList.forEach((promise, index) => {
_Promise.resolve(promise).then(result => {
count++
valueList[index] = result // 将每次返回的结果搜集起来
if (count === promiseList.length) {
// 表示所有的promise都有结果,最终将所有的结果都resolve出去
resolve(valueList)
}
}, err => reject(err))
})
})
}

看看,其实很简单。

Recoil API手册--Selector(options)

Selectors represent a function, or derived state in Recoil. You can think of them as similar to an “idempotent” or “pure function” without side-effects that always returns the same value for a given set of dependency values. If only a get function is provided, the selector is read-only and returns a RecoilValueReadOnly object. If a set is also provided, it returns a writeable RecoilState object.

Recoil manages atom and selector state changes to know when to notify components subscribing to that selector to re-render. If an object value of a selector is mutated directly it may bypass this and avoid properly notifying subscribing components. To help detect bugs, Recoil will freeze selector value objects in development mode.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function selector<T>({
key: string,

get: ({
get: GetRecoilValue
}) => T | Promise<T> | RecoilValue<T>,

set?: (
{
get: GetRecoilValue,
set: SetRecoilState,
reset: ResetRecoilState,
},
newValue: T | DefaultValue,
) => void,

dangerouslyAllowMutability?: boolean,
})
1
2
3
4
type ValueOrUpdater<T> = T | DefaultValue | ((prevValue: T) => T | DefaultValue);
type GetRecoilValue = <T>(RecoilValue<T>) => T;
type SetRecoilState = <T>(RecoilState<T>, ValueOrUpdater<T>) => void;
type ResetRecoilState = <T>(RecoilState<T>) => void;
  • key - A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application. It needs to be stable across executions if used for persistence.

  • get - A function that evaluates the value for the derived state. It may return either a value directly or an asynchronous Promise or another atom or selector representing the same type. It is passed an object as the first parameter containing the following properties:

    • get - a function used to retrieve values from other atoms/selectors. All atoms/selectors passed to this function will be implicitly added to a list of dependencies for the selector. If any of the selector’s dependencies change, the selector will re-evaluate.
  • set? - If this property is set, the selector will return writeable state. A function that is passed an object of callbacks as the first parameter and the new incoming value. The incoming value may be a value of type T or maybe an object of type DefaultValue if the user reset the selector. The callbacks include:

    • get - a function used to retrieve values from other atoms/selectors. This function will not subscribe the selector to the given atoms/selectors.
    • set - a function used to set the values of upstream Recoil state. The first parameter is the Recoil state and the second parameter is the new value. The new value may be an updater function or a DefaultValue object to propagate reset actions.
  • dangerouslyAllowMutability - In some cases it may be desireable allow mutating of objects stored in selectors that don’t represent state changes. Use this option to override freezing objects in development mode.


A selector with a simple static dependency:

1
2
3
4
const mySelector = selector({
key: 'MySelector',
get: ({get}) => get(myAtom) * 100,
});

Dynamic Dependencies

A read-only selector has a get method which evaluates the value of the selector based on dependencies. If any of those dependencies are updated, then the selector will re-evaluate. The dependencies are dynamically determined based on the atoms or selectors you actually use when evaluating the selector. Depending on the values of the previous dependencies, you may dynamically use different additional dependencies. Recoil will automatically update the current data-flow graph so that the selector is only subscribed to updates from the current set of dependencies.

In this example mySelector will depend on the toggleState atom as well as either selectorA or selectorB depending on the state of toggleState.

1
2
3
4
5
6
7
8
9
10
11
12
13
const toggleState = atom({key: 'Toggle', default: false});

const mySelector = selector({
key: 'MySelector',
get: ({get}) => {
const toggle = get(toggleState);
if (toggle) {
return get(selectorA);
} else {
return get(selectorB);
}
},
});

Writeable Selectors

A bi-directional selector receives the incoming value as a parameter and can use that to propagate the changes back upstream along the data-flow graph. Because the user may either set the selector with a new value or reset the selector, the incoming value is either of the same type that the selector represents or a DefaultValue object which represents a reset action.

This simple selector essentially wraps an atom to add an additional field. It just passes through set and reset operations to the upstream atom.

1
2
3
4
5
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});

This selector transforms the data, so needs to check if the incoming value is a DefaultValue.

1
2
3
4
5
6
const transformSelector = selector({
key: 'TransformSelector',
get: ({get}) => get(myAtom) * 100,
set: ({set}, newValue) =>
set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});

Asynchronous Selectors

Selectors may also have asynchronous evaluation functions and return a Promise to the output value. Please see this guide for more information.

1
2
3
4
5
6
const myQuery = selector({
key: 'MyQuery',
get: async ({get}) => {
return await myAsyncQuery(get(queryParamState));
}
});

Example (Synchronous)

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
import {atom, selector, useRecoilState, DefaultValue} from 'recoil';

const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});

const tempCelsius = selector({
key: 'tempCelsius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) =>
set(
tempFahrenheit,
newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32
),
});

function TempCelsius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);
const resetTemp = useResetRecoilState(tempCelsius);

const addTenCelsius = () => setTempC(tempC + 10);
const addTenFahrenheit = () => setTempF(tempF + 10);
const reset = () => resetTemp();

return (
<div>
Temp (Celsius): {tempC}
<br />
Temp (Fahrenheit): {tempF}
<br />
<button onClick={addTenCelsius}>Add 10 Celsius</button>
<br />
<button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
<br />
<button onClick={reset}>>Reset</button>
</div>
);
}

Example (Asynchronous)

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
import {selector, useRecoilValue} from 'recoil';

const myQuery = selector({
key: 'MyDBQuery',
get: async () => {
const response = await fetch(getMyRequestUrl());
return response.json();
},
});

function QueryResults() {
const queryResults = useRecoilValue(myQuery);

return (
<div>
{queryResults.foo}
</div>
);
}

function ResultsSection() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<QueryResults />
</React.Suspense>
);
}

Please see this guide for more complex examples.

微信系列--Android机微信小程序手写输入不能获取完整字符

问题描述:
微信小程序表单输入使用手写输入法,当输入完成后,点击空白处键盘收起,这时,绑定的input事件或者是change事件并不能获取到完整的输入内容。原因在于,当输入的文字有下划线的时候,手机不认为输入完成

1
2
3
4
<van-field label="姓名" placeholder="请输入姓名" bind:change="getUsername" border="{{false}}" value="{{form.userName}}" readonly="{{isReadonly}}"></van-field>
getUsername (value) {
this.setData({'form.userName': value.detail})
},

解决兼容性问题的方法是添加blur事件。

1
2
3
4
5
6
7
<van-field label="姓名" placeholder="请输入姓名" bind:blur="getUsernames" bind:change="getUsername" border="{{false}}" value="{{form.userName}}" readonly="{{isReadonly}}"></van-field>
getUsername (value) {
this.setData({'form.userName': value.detail})
},
getUsernames (value) {
this.setData({'form.userName': value.detail.value})
},

这样可以保证在失去焦点的时候,能获取到全部的输入值。

微信系列--微信小程序授权登陆流程

小程序开发了这么久。无论是工作还是面试,总会遇到一些问题,需要去总结。登陆问题一直以来都是面试考察的重点,所以我来总结一下小程序登陆的流程。
首先,我们需要搞明白,登陆是为了什么?
这么说其实有点蠢,但是我们又不得不这么说,因为很多人不知道登陆是干什么,其实从页面效果来看呢,就是要拿到用户信息展示在页面上,从技术角度来说呢,就是要拿到登陆的凭证–token。仅此而已,OK,知道了这俩点呢。我们就可以开干了。

手动授权登陆

在手动授权之前,我们可以先公共微信提供的API来检查用户是不是已经授权过:

1
wx.getSetting()

如果没有授权,那么在新版本的小程序登陆中,微信做了限制,要求用户必须手动,所以我们需要在页面中写点代码:

1
<button class='bottom' type='primary' open-type="getUserInfo" lang="zh_CN" bindgetuserinfo="bindGetUserInfo">授权登录</button>
  • 这里要求必须设置open-type=”getUserInfo”
    这样点击按钮:
    登陆按钮
    就可以调起授权弹窗了。
    手动授权弹窗
    点允许,那么授权成功,这时会拿到用户的一些信息,
    用户信息
    这样我们再调用:

    1
    wx.login()

    用来获取微信的临时凭证,将获取到的信息和appid(小程序管理后台获取)一并发给我们自己的服务端,我们的服务端会拿着这些东西去微信服务器换取openid和session_key,这样我们自己的服务端就可以生成自己自定义的登陆态(token),r然后返回给前端,前端存储在localStorage.就可以发起业务请求了。
    alt 属性文本

  • 某些业务场景可能会需要手机号,因为获取手机号。所以需要调用,这里也必须要用户自己触发,且必须设置open-type=”getPhoneNumber”:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机号</button>
    getPhoneNumber(e) {
    const that = this;
    if (e.detail.errMsg !== 'getPhoneNumber:ok') {
    wx.showToast({
    title: '获取手机号失败',
    icon: 'none',
    });
    return;
    }
    }

    微信授权登陆,大致就是这样!!!!!!!!!!!!!

Recoil API手册--atom(options)

An atom represents state in Recoil. The atom() function returns a writeable RecoilState object.
atom代表了Recoil中的状态,atom()返回了一个可读写的Recoil状态对象。


1
2
3
4
5
6
function atom<T>({
key: string,
default: T | Promise<T> | RecoilValue<T>,
effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
  • key - A unique string used to identify the atom internally. This string should be unique with respect to other atoms and selectors in the entire application.
    key字段是一个用来定义内部atom的唯一的字符串,这个是字符串必须是唯一的,用来区分应用程序中所有的atoms和selector.
  • default - The initial value of the atom or a Promise or another atom or selector representing a value of the same type.
    default字段用来初始化一个atom或者Promise或者另一个atom或者是selector具有相同类型的初始值。
  • effects_UNSTABLE - An optional array of Atom Effects for the atom.
    effects_UNSTABLE字段一个可选的atom副作用数组。
  • dangerouslyAllowMutability - In some cases it may be desireable allow mutating of objects stored in atoms that don’t represent state changes. Use this option to override freezing objects in development mode.
    dangerouslyAllowMutability在某些情况下,他是用来描述允许atoms存储的对象发生转化,但是并不代表状态会发生变化。可以用这个选项在开发环境来冻结对象。

Recoil manages atom state changes to know when to notify components subscribing to that atom to re-render, so you should use the hooks listed below to change atom state. If an object stored in an atom was mutated directly it may bypass this and cause state changes without properly notifying subscribing components. To help detect bugs Recoil will freeze objects stored in atoms in development mode.

Recoil管理atom状态是通过通知组建订阅atom状态实现重新渲染,所以,应该使用下面的hooks函数来使得atom状态发生变化。如果一个存储在atom中的对象被直接改变,那就会引起组建没有被通知订阅而atom状态发生变化。为了解决这个bug,Recoil会在开发环境冻结存储在atom中的对象。

Most often, you’ll use the following hooks to interact with atoms:
通常情况下,需要使用下列的hooks函数来使得atoms发生变化。

  • useRecoilState(): Use this hook when you intend on both reading and writing to the atom. This hook subscribes the component to the atom.
    useRecoilState():当你要读写atom的时候使用这个钩子函数,这个钩子函数会订阅组建的atom.
  • useRecoilValue(): Use this hook when you intend on only reading the atom. This hook subscribes the component to the atom.
    useRecoilValue():当只是读取atom的时候。使用这个钩子函数,这个钩子函数会订阅组建的atom.
  • useSetRecoilState(): Use this hook when you intend on only writing to the atom.
    useSetRecoilState():当只是操作atom的时候。使用这个钩子函数。
  • useResetRecoilState(): Use this hook to reset an atom to its default value.
    useResetRecoilState():当重置atom的默认值的时候,使用这个钩子函数。
    For rare cases where you need to read an atom’s value without subscribing to the component, see useRecoilCallback().

对于极少数需要读取atom的值,而不需要订阅组建,可以参考useRecoilCallback()

You can initialize an atom either with a static value or with a Promise or a RecoilValue representing a value of the same type. Because the Promise may be pending or the default selector may be asynchronous it means that the atom value may also be pending or throw an error when reading. Note that you cannot currently assign a Promise when setting an atom. Please use selectors for async functions.

可以用通过一个具有共同类型的静态值或者是一个Promise或者是一个RecoilValue来初始化atom.因为Promise也许是进行中,或者默认的selector会是异步的,这就会使得atom的状态也是处于进行中或者是抛出一个错误。所以,当是这atom的时候,并不能将当前标注为Promise,请使用selector异步函数。

Atoms cannot be used to store Promise's or RecoilValue's directly, but they may be wrapped in an object. Note that Promise's may be mutable. Atoms can be set to a function, as long as it is pure, but to do so you may need to use the updater form of setters. (e.g. set(myAtom, () => myFunc);).

Atoms不能被直接才存储为Promise或者是RecoilValue's, 但是他们可以被包裹为一个对象。注意Promise可能被改变。Atoms可以被设置为是个函数,只要纯净,就可以使用setters的更新方式。(e.g. set(myAtom, () => myFunc);).

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {atom, useRecoilState} from 'recoil';

const counter = atom({
key: 'myCounter',
default: 0,
});

function Counter() {
const [count, setCount] = useRecoilState(counter);
const incrementByOne = () => setCount(count + 1);

return (
<div>
Count: {count}
<br />
<button onClick={incrementByOne}>Increment</button>
</div>
);
}

LeetCode算法--求俩数之和

题目:给定一个数组nums和一个目标值target,从数组中找到俩个元素的和等于目标值target,并返回这俩个元素的位置。
思路一:暴力算法,将数组中的俩个元素相加,如果等于目标值target,那么返回其位置。

1
2
3
4
5
6
7
8
9
10
11
let fun = (nums, target) => {
let result = []
for (let i = 0; i < nums.length - 1; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
result.push(i, j)
}
}
}
return result
}

注意:
1,这里需要注意的是循环的边界值,i < nums.length - 1, j < nums.length
2,因为做了俩层循环,所以时间复杂度是O(n2),声明了三个变量,空间复杂度是O(n)

思路二:由于上面的暴力算法时间复杂度比较高,所以我们可以再时间复杂度上做一点优化,我们可不可以将时间复杂度降低到O(N)呢?当然是可以的,对于每一个 nums[ i ],我们首先查询哈希表中是否存在 target - nums[ i ],然后将 nums[ i ] 插入到哈希表中,即可保证不会让 nums[ i ] 和自己匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let fun = (nums, target) => {
let result = []
let map = new Map()
for (let i = 0; i < nums.length; i++) {
map.set(nums[i], i);
}
for (let i = 0; i < nums.length;i++) {
let item = target - nums[i]
if (map.has(item) && map.get(item) !== i) {
result.push(i);
result.push(map.get(item))
break
}
}
return result
}

注意:
我们首先创建了一个map结构,用来保存数组中的元素和位置,作为哈希表。然后按照思路二来实现。遍历一次,也降低了时间复杂度。

Recoil API手册--<RecoilRoot />

<RecoilRoot …props />

Provides the context in which atoms have values. Must be an ancestor of any component that uses any Recoil hooks. Multiple roots may co-exist; atoms will have distinct values within each root. If they are nested, the innermost root will completely mask any outer roots.
为atoms提供上下文。必须是所有是使用Recoil hooks组建的根组建,多个根组建也许可以共存,atoms需要对不同的根组建进行区别。如果它们是嵌套的,最里面的根将完全掩盖任何外部根。
Props:
initializeState?: (MutableSnapshot => void)
An optional function that takes a MutableSnapshot to initialize the atom state. This sets up the state for the initial render and is not intended for subsequent state changes or async initialization. Use hooks such as useSetRecoilState() or useRecoilCallback() for async state changes.
通过一个可选函数来初始化根RecoilRoot组建的atom状态。这将设置初始呈现的状态,而不是用于后续状态更改或异步初始化。利用hooks函数如useSetRecoilState()或者useRecoilCallback()实现异步更新状态。

<RecoilRoot>'s represent independent providers/stores of atom state. Note that caches, such as selector caches, may be shared across roots. Selector evaluations must be idempotent except for caching or logging, so this should not be a problem, but may be observable or may cause redundant queries to be cached across roots.
组建是atom状态的独立的Provider/store.请注意,缓存(如选择器缓存)可以跨根共享。选择器求值必须是幂等的(除了缓存或日志记录),所以这应该不是问题,但可能是可观察的,或者可能导致在根之间缓存冗余查询。

例子

1
2
3
4
5
6
7
8
9
import {RecoilRoot} from 'recoil';

function AppRoot() {
return (
<RecoilRoot>
<ComponentThatUsesRecoil />
</RecoilRoot>
);
}

Recoil指南--Dev Tools 开发工具

Dev Tools 开发工具

Recoil has functionality to allow you to observe and update state changes.
Recoil可以允许您监听并且更新状态变化。


IMPORTANT NOTE 重点关注

This API is currently under development and will change. Please stay tuned…
这个API当前还在开发中,并且会有变化,请稍等…


Observing All State Changes 监听所有的状态变化

You can use a hook such as useRecoilSnapshot() or useRecoilTransactionObserver_UNSTABLE() to subscribe to state changes and obtain a Snapshot of the new state.
可以使用诸如useRecoilSnapshot()或者useRecoilTransactionObserver_UNSTABLE()的钩子函数来订阅状态的变化,或者是添加一个Snapshot的新状态。

Once you have a Snapshot, you can use methods such as getLoadable(), getPromise(), and getInfo() to inspect the state and use getNodes() to iterate over the set of known atoms.
一旦使用Snapshot,你就可以用类似于getLoadable(), getPromise(), 和 getInfo()方法去检查state,用getNodes()方法来迭代atoms.

1
2
3
4
5
6
7
8
9
10
11
function DebugObserver(): React.Node {
const snapshot = useRecoilSnapshot();
useEffect(() => {
console.debug('The following atoms were modified:');
for (const node of snapshot.getNodes({modified: true})) {
console.debug(node.key, snapshot.getLoadable(node));
}
}, [snapshot]);

return null;
}
1
2
3
4
5
6
7
8
function MyApp() {
return (
<RecoilRoot>
<DebugObserver />
...
</RecoilRoot>
);
}

Observing State Changes On-Demand 按需监听state的变化

Or, you can use the useRecoilCallback() hook to obtain a Snapshot on-demand.
可以使用使用useRecoilCallback()hook函数来获取按需Snapshot

1
2
3
4
5
6
7
8
9
10
11
function DebugButton(): React.Node {
const onClick = useRecoilCallback(({snapshot}) => async () => {
console.debug('Atom values:');
for (const node of snapshot.getNodes()) {
const value = await snapshot.getPromise(node);
console.debug(node.key, value);
}
}, []);

return <button onClick={onClick}>Dump State</button>
}

Time Travel

The useGotoRecoilSnapshot() hook can be used to update the entire Recoil state to match the provided Snapshot. This example maintains a history of state changes with the ability to go back and restore previous global state.
useGotoRecoilSnapshot() 钩子函数可以用来更新全部的Recoil状态来匹配所提供的Snapshot,此示例维护了状态更改的历史,并能够返回和恢复以前的全局状态。
Snapshot's also provide a getID() method. That can be used, for example, to help determine if you are reverting to a previous known state to avoid updating your snapshot history.
Snapshot同样提供了getID()的方法。例如,可以使用它来帮助确定您是否正在恢复到以前的已知状态,以避免更新快照历史记录。

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
function TimeTravelObserver() {
const [snapshots, setSnapshots] = useState([]);

const snapshot = useRecoilSnapshot();
useEffect(() => {
if (snapshots.every(s => s.getID() !== snapshot.getID())) {
setSnapshots([...snapshots, snapshot]);
}
}, [snapshot]);

const gotoSnapshot = useGotoRecoilSnapshot();

return (
<ol>
{snapshots.map((snapshot, i) => (
<li key={i}>
Snapshot {i}
<button onClick={() => gotoSnapshot(snapshot)}>
Restore
</button>
</li>
))}
</ol>
);
}

Recoil指南--Testing 测试

Testing Recoil Selectors outside of React React之外测试Recoil的Selectors

It can be useful to manipulate and evaluate Recoil selectors outside of a React context for testing. This can be done by working with a Recoil Snapshot. You can build a fresh snapshot using snapshot_UNSTABLE() and then use that Snapshot to evaluate selectors for testing.
脱离React来测试Recoil的Selectors是非常有用的。这可以通过Recolid的Snapshot来实现。可以通过snapshot_UNSTABLE()函数来创建一个全新的snapshot,然后用Snapshot来实现selector的测试。

Example: Jest unit testing selectors Selectors的单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const numberState = atom({key: 'Number', default: 0});

const multipliedState = selector({
key: 'MultipliedNumber',
get: ({get}) => get(numberState) * 100,
});

test('Test multipliedState', () => {
const initialSnapshot = snapshot_UNSTABLE();
expect(mySnapshot.getLoadable(multipliedState).valueOrThrow()).toBe(0);

const testSnapshot = snapshot_UNSTABLE(({set}) => set(numberState, 1));
expect(mySnapshot.getLoadable(multipliedState).valueOrThrow()).toBe(100);
})

手动实现Javascript中new操作符

想要实现new操作符,首先要知道new操作符都干了什么事儿?

  • 创建了一个空,将this指向这个对象
  • 将创建的空对象的__proto__属性继承自构造函数的prototype属性,也就是说是继承构造函数的原型对象上的方法和属性
  • 调用构造函数,将构造函数中的this替换为空对象的this,继承构造函数中的属性
  • 返回函数内部的这个新对象
1
2
3
4
5
6
7
8
function _new (fun) {
return function () {
let obj = {}
obj.__proto__ = fun.prototype
fun.call(obj, ...arguments)
return obj
}
}

测试:

1
2
3
4
5
function Person (name, age) {
this.name = name
this.age = age
}
let person = _new(Person)('小明', 3) //=> {name: '小明', age: 3}

Recoil指南--Atom Effects Atom副作用

Atom Effects Atom副作用

Atom Effects are a new experimental API for managing side-effects and initializing Recoil atoms. They have a variety of useful applications such as state persistence, state synchronization, managing history, logging, &c. They are defined as part of the atom definition, so each atom can specify and compose their own policies. This API is still evolving, and thus marked as _UNSTABLE.
Atom Effects是为了管理副作用和初始化Recoil Atom的实验性的API。它会有多种应用,例如:状态持久化,同步状态,历史管理,日志等。这些都被定义为Atom最初定义的一部分。所以每个部分都可以有指定或者组合自己的策略。这些API还在开发中,所以暂时就把它称作_UNSTABLE版本吧。


IMPORTANT NOTE 重要提示

This API is currently under development and will change. Please stay tuned…
这些API还在开发中。并且会有变化,所以再等等。。。。


An atom effect is a function with the following definition.
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
type AtomEffect<T> = ({
node: RecoilState<T>, // A reference to the atom itself
trigger: 'get' | 'set', // The action which triggered initialization of the atom

// Callbacks to set or reset the value of the atom.
// This can be called from the atom effect function directly to initialize the
// initial value of the atom, or asynchronously called later to change it.
setSelf: (
| T
| DefaultValue
| Promise<T | DefaultValue> // Only allowed for initialization at this time
| ((T | DefaultValue) => T | DefaultValue),
) => void,


resetSelf: () => void,

// Subscribe to changes in the atom value.
// The callback is not called due to changes from this effect's own setSelf().
onSet: (
(newValue: T | DefaultValue, oldValue: T | DefaultValue) => void,
) => void,

}) => void | () => void; // Optionally return a cleanup handler

Atom effects are attached to atoms via the effects_UNSTABLE option. Each atom can reference an array of these atom effect functions which are called in priority order when the atom is initialized. Atoms are initialized when they are used for the first time within a <RecoilRoot>, but may be re-initialized again if they were unused and cleaned up. The atom effect function may return an optional cleanup handler to manage cleanup side-effects.
Atom 副作用是通过Atom的effects_UNSTABLE来添加的。在Atom初始化的时候。每一个Atom都可以引用一个按照优先级调用的atom副作用函数的数组。当第一次应用于<RecoilRoot>时,Atom被初始化。但是也许他们没有被调用或者没有被清除,就会被再次初始化。Atom副作用函数会返回一个可选的处理副作用的函数。

1
2
3
4
5
6
7
8
9
10
11
const myState = atom({
key: 'MyKey',
default: null,
effects_UNSTABLE: [
() => {
...effect 1...
return () => ...cleanup effect 1...;
},
() => { ...effect 2... },
],
});

Atom families also support parameterized or non-parameterized effects:
Atom也支持参数化或者无参化:

1
2
3
4
5
6
7
8
9
10
11
const myStateFamily = atomFamily({
key: 'MyKey',
default: null,
effects_UNSTABLE: param => [
() => {
...effect 1 using param...
return () => ...cleanup effect 1...;
},
() => { ...effect 2 using param... },
],
});

Compared to React Effects 与React副作用相比

Atom effects could mostly be implemented via React useEffect(). However, the set of atoms are created outside of a React context, and it can be difficult to manage effects from within React components, particularly for dynamically created atoms. They also cannot be used to initialize the initial atom value or be used with server-side rendering. Using atom effects also co-locates the effects with the atom definitions.
Atom副作用主要是通过React的useEffect钩子函数来生效。然后atoms是在React上下文之外被创建的。并且在React组建内部管理副作用是非常困难的。特别是动态生成的atoms.这些atoms也常常并不能被用来初始化初始的atom值,或者是用来做服务端渲染。通常atom副作是和定义atom在同一处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const myState = atom({key: 'Key', default: null});

function MyStateEffect(): React.Node {
const [value, setValue] = useRecoilState(myState);
useEffect(() => {
// Called when the atom value changes
store.set(value);
store.onChange(setValue);
return () => { store.onChange(null); }; // Cleanup effect
}, [value]);
return null;
}

function MyApp(): React.Node {
return (
<div>
<MyStateEffect />
...
</div>
);
}

Compared to Snapshots

The Snapshot hooks API can also monitor atom state changes and the initializeState prop in can initialize values for initial render. However, these APIs monitor all state changes and can be awkward to manage dynamic atoms, particularly atom families. With atom effects, the side-effect can be defined per-atom alongside the atom definition and multiple policies can be easily composed.
Snapshot hooks钩子API可以监听Atom状态的变化并且为初次渲染初始化RecoilRoot组件的初始值。然后,这些API监听所有的状态变化,并且很难管理动态的Atom.尤其是atom系列。有了atom副作用。一些副作用,就可以通过atom提前定义,并且和多种方案组合在一起。

Logging Example

A simple example of using atom effects are for logging a specific atom’s state changes.
一个简单的使用atom副作用来记录特定atom状态的变化:

1
2
3
4
5
6
7
8
9
10
11
const currentUserIDState = atom({
key: 'CurrentUserID',
default: null,
effects_UNSTABLE: [
({onSet}) => {
onSet(newID => {
console.debug("Current user ID:", newID);
});
},
],
});

History Example

A more complex example of logging might maintain a history of changes. This example provides an effect which maintains a history queue of state changes with callback handlers that undo that particular change:
一个更复杂的例子是用来维护变化的历史记录。此示例提供了一个效果,它使用撤消特定更改的回调处理程序维护状态更改的历史队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const history: Array<{
label: string,
undo: () => void,
}> = [];

const historyEffect = name => ({setSelf, onSet}) => {
onSet((newValue, oldValue) => {
history.push({
label: `${name}: ${JSON.serialize(oldValue)} -> ${JSON.serialize(newValue)}`,
undo: () => {
setSelf(oldValue);
},
});
});
};

const userInfoState = atomFamily({
key: 'UserInfo',
default: null,
effects_UNSTABLE: userID => [
historyEffect(`${userID} user info`),
],
});

State Synchronization Example 状态同步案例

It can be useful to use atoms as a local cached value of some other state such as a remote database, local storage, &c. You could set the default value of an atom using the default property with a selector to get the store’s value. However, that is only a one-time lookup; if the store’s value changes the atom value will not change. With effects, we can subscribe to the store and update the atom’s value whenever the store changes. Calling setSelf() from the effect will initialize the atom to that value and will be used for the initial render. If the atom is reset, it will revert to the default value, not the initialized value.
将atom作为诸如,远程数据库,本地存储等的本地缓存状态是非常有用的。可以通过selector设置默认属性给atom设置默认的值来获取存储的值。然后,这只是一次性的。如果存储中的值发生了变化,而atom的值是不会有变化的。通过副作用,无论何时,我们都可以订阅存储中的值,并且更新atom的值。从副作用中调用setSelf()函数来初始化atom并且初次渲染。如果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
25
const syncStorageEffect = userID => ({setSelf, trigger}) => {
// Initialize atom value to the remote storage state
if (trigger === 'get') { // Avoid expensive initialization
setSelf(myRemoteStorage.get(userID)); // Call synchronously to initialize
}

// Subscribe to remote storage changes and update the atom value
myRemoteStorage.onChange(userID, userInfo => {
setSelf(userInfo); // Call asynchronously to change value
});

// Cleanup remote storage subscription
return () => {
myRemoteStorage.onChange(userID, null);
};
};

const userInfoState = atomFamily({
key: 'UserInfo',
default: null,
effects_UNSTABLE: userID => [
historyEffect(`${userID} user info`),
syncStorageEffect(userID),
],
});

Write-Through Cache Example

We can also bi-directionally sync atom values with remote storage so changes on the server update the atom value and changes in the local atom are written back to the server. The effect will not call the onSet() handler when changed via that effect’s setSelf() to help avoid feedback loops.
我们也可以通过在服务器上的远程存储来更新atom的值也可以重写本地atom的值到服务端的方式来实现双向同步atom的值。当通过副作用的setSelf()来循环调用的时候,副作用不会调用onSet()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const syncStorageEffect = userID => ({setSelf, onSet, trigger}) => {
// Initialize atom value to the remote storage state
if (trigger === 'get') { // Avoid expensive initialization
setSelf(myRemoteStorage.get(userID)); // Call synchronously to initialize
}

// Subscribe to remote storage changes and update the atom value
myRemoteStorage.onChange(userID, userInfo => {
setSelf(userInfo); // Call asynchronously to change value
});

// Subscribe to local changes and update the server value
onSet(userInfo => {
myRemoteStorage.set(userID, userInfo);
});

// Cleanup remote storage subscription
return () => {
myRemoteStorage.onChange(userID, null);
};
};

Local Storage Persistence 本地存储持久化

Atom effects can be used to persist atom state with browser local storage. Note that the following examples are simplified for illustrative purposes and do not cover all cases.
Atom副作用可以通过浏览器本地存储做atom状态的持久化。注意下面的例子,非常简明的说明了这点,但并不能涵盖所有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const localStorageEffect = key => ({setSelf, onSet}) => {
const savedValue = localStorage.getItem(key)
if (savedValue != null) {
setSelf(JSON.parse(savedValue));
}

onSet(newValue => {
if (newValue instanceof DefaultValue) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, JSON.stringify(newValue));
}
});
};

const currentUserIDState = atom({
key: 'CurrentUserID',
default: 1,
effects_UNSTABLE: [
localStorageEffect('current_user'),
]
});

Backward Compatibility

What if you change the format for an atom? Loading a page with the new format with a localStorage based on the old format could case a problem. You could build effects to handle restoring and validating the value in a type safe way:
要是你改变了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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type PersistenceOptions<T>: {
key: string,
restorer: (mixed, DefaultValue) => T | DefaultValue,
};

const localStorageEffect = <T>(options: PersistenceOptions<T>) => ({setSelf, onSet}) => {
const savedValue = localStorage.getItem(options.key)
if (savedValue != null) {
setSelf(options.restorer(JSON.parse(savedValue), new DefaultValue()));
}

onSet(newValue => {
if (newValue instanceof DefaultValue) {
localStorage.removeItem(options.key);
} else {
localStorage.setItem(options.key, JSON.stringify(newValue));
}
});
};

const currentUserIDState = atom<number>({
key: 'CurrentUserID',
default: 1,
effects_UNSTABLE: [
localStorageEffect({
key: 'current_user',
restorer: (value, defaultValue) =>
// values are currently persisted as numbers
typeof value === 'number'
? value
// if value was previously persisted as a string, parse it to a number
: typeof value === 'string'
? parseInt(value, 10)
// if type of value is not recognized, then use the atom's default value.
: defaultValue
}),
],
});

What if the key used to persist the value changes? Or what used to be persisted using one key now uses several? Or vice versa? That can also be handled in a type-safe way:
要是持久化数据的key变化了怎么办?或者是之前用一个,现在用了多个key怎么办?反之亦然。这也依然可以通过安全的方式来处理:

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
type PersistenceOptions<T>: {
key: string,
restorer: (mixed, DefaultValue, Map<string, mixed>) => T | DefaultValue,
};

const localStorageEffect = <T>(options: PersistenceOptions<T>) => ({setSelf, onSet}) => {
const savedValues = parseValuesFromStorage(localStorage);
const savedValue = savedValues.get(options.key);
setSelf(
options.restorer(savedValue ?? new DefaultValue(), new DefaultValue(), savedValues),
);

onSet(newValue => {
if (newValue instanceof DefaultValue) {
localStorage.removeItem(options.key);
} else {
localStorage.setItem(options.key, JSON.stringify(newValue));
}
});
};

const currentUserIDState = atom<number>({
key: 'CurrentUserID',
default: 1,
effects_UNSTABLE: [
localStorageEffect({
key: 'current_user',
restorer: (value, defaultValue, values) => {
if (typeof value === 'number') {
return value;
}

const oldValue = values.get('old_key');
if (typeof oldValue === 'number') {
return oldValue;
}

return defaultValue;
},
}),
],
});

Browser URL History Persistence 浏览器URL历史数据持久化

Atom state can also be persisted and synced with the browser URL history. This can be useful to have state changes update the current URL so it can be saved or shared with others to restore that state. It can also be integrated with the browser history to leverage the browser forward/back buttons. Examples or a library to provide this type of persistence are coming soon…
浏览器同样可以用来做浏览器URL历史的数据持久化和数据的同步。这在处理更新当前URL状态是非常有用的,这对于保存或者和与别的Atom共享重新存储状态是非常有用的。它还可以与浏览器历史记录集成,以利用浏览器前进/后退按钮。提供这个功能的案例或者是库正在开发中…

Recoil指南--Asynchronous Data Queries 异步数据请求

Recoil provides a way to map state and derived state to React components via a data-flow graph. What’s really powerful is that the functions in the graph can also be asynchronous. This makes it easy to use asynchronous functions in synchronous React component render functions. Recoil allows you to seamlessly mix synchronous and asynchronous functions in your data-flow graph of selectors. Simply return a Promise to a value instead of the value itself from a selector get callback, the interface remains exactly the same. Because these are just selectors, other selectors can also depend on them to further transform the data.
Recoil通过数据流图为react提供了俩中state.更为强大的是,函数式的数据流可以是异步的。这使得在同步的react组件中使用异步函数渲染变的非常容易。在Recoil的selector数据流中可以允同步和异步方法混合使用,翻译一个Promise来获取值,而不是Selector的get回调中。接口也是同样的类似,因为这些仅仅是Selector,其他的Selector可以依赖于这些Selector做更多的数据转化。
Selectors can be used as one way to incorporate asynchronous data into the Recoil data-flow graph. Please keep in mind that selectors represent “idempotent” functions: For a given set of inputs they should always produce the same results (at least for the lifetime of the application). This is important as selector evaluations may be cached, restarted, or executed multiple times. Because of this, selectors are generally a good way to model read-only DB queries. For mutable data you can use a Query Refresh or to synchronize mutable state, persist state, or for other side-effects consider the experimental Atom Effects API.
Selector可以被视作为是注册异步数据的一种方式。切记,selector代表了’幂等’函数:对于给定的一组输入,它们应该总是产生相同的结果(至少在应用程序的生命周期内),这一点非常的重要,因为selector的运算可能被缓存,重新计算,或者被执行多次。尽管如此,Selector也是一个读取数据查询非常好的方式。对于那些可变的数据,可以刷新请求或者同步可变状态,持久状态或者可以考虑其他的副作用的Atom Api.

Synchronous Example 同步案例

For example, here is a simple synchronous atom and selector to get a user name:
例如,下面是一个简单的异步获取用户名的AtomSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const currentUserIDState = atom({
key: 'CurrentUserID',
default: 1,
});

const currentUserNameState = selector({
key: 'CurrentUserName',
get: ({get}) => {
return tableOfUsers[get(currentUserIDState)].name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameState);
return <div>{userName}</div>;
}

function MyApp() {
return (
<RecoilRoot>
<CurrentUserInfo />
</RecoilRoot>
);
}

Asynchronous Example 异步案例

If the user names were stored in some database we need to query, all we need to do is return a Promise or use an async function. If any dependencies change, the selector will be re-evaluated and execute a new query. The results are cached, so the query will only execute once per unique input.
如果我们需要的用户名是被存储到某个数据库,我们需要做的就是就是通过async方法返回一个Promise.如果一些依赖项发生变化,Selector也会重新计算,或者是重新发起请求。结果会被缓存,所以请求只会被执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const currentUserNameQuery = selector({
key: 'CurrentUserName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}

The interface of the selector is the same, so the component using this selector doesn’t need to care if it was backed with synchronous atom state, derived selector state, or asynchronous queries!
Selector的接口也是一样的,因此组件中用到Selector并不需要关心是否支持同步的Atom 状态,派生Selector状态,或者是异步请求。
But, since React render functions are synchronous, what will it render before the promise resolves? Recoil is designed to work with React Suspense to handle pending data. Wrapping your component with a Suspense boundary will catch any descendants that are still pending and render a fallback UI:
但是,由于react的渲染是同步的,那么在promise reslove之前什么会被渲染?Recoil设计使用react Suspense来处理进行中的数据。将组件以及其后代组件暂时都挂起,返回等待中的UI界面:

1
2
3
4
5
6
7
8
9
function MyApp() {
return (
<RecoilRoot>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
</RecoilRoot>
);
}

Error Handling 错误处理

But what if the request has an error? Recoil selectors can also throw errors which will then be thrown if a component tries to use that value. This can be caught with a React . For example:
一旦请求出现错误怎么办?Recoil Selector可以扔出一个React组件可以捕获的错误。这个错误可以被React的组件捕获。案例:

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
const currentUserNameQuery = selector({
key: 'CurrentUserName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
if (response.error) {
throw response.error;
}
return response.name;
},
});

function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}

function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}

Queries with Parameters 带参请求

Sometimes you want to be able to query based on parameters that aren’t just based on derived state. For example, you may want to query based on the component props. You can do that using the selectorFamily helper:
有时候你可能想要发起一个携带参数带请求的不仅仅依赖于派生状态。例如,你的请求是基于组件的props,那么你可以使用selectorFamily

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
const userNameQuery = selectorFamily({
key: 'UserName',
get: userID => async () => {
const response = await myDBQuery({userID});
if (response.error) {
throw response.error;
}
return response.name;
},
});

function UserInfo({userID}) {
const userName = useRecoilValue(userNameQuery(userID));
return <div>{userName}</div>;
}

function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<UserInfo userID={1}/>
<UserInfo userID={2}/>
<UserInfo userID={3}/>
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}

Data-Flow Graph 数据流

Remember, by modeling queries as selectors, we can build a data-flow graph mixing state, derived state, and queries! This graph will automatically update and re-render React components as state is updated.
切记,我们可以通过模块查询来创建混合状态,派生状态,以及请求的Selector。这样数据状态一旦更新React的组件会自动更新并重新渲染。
The following example will render the current user’s name and a list of their friends. If a friend’s name is clicked on, they will become the current user and the name and list will be automatically updated.
下面的例子将会渲染当前的用户名,以及朋友列表,如果某一个朋友的名字被点击。他将会变成当前的用户,并且姓名和裂变会自动更新。

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
49
50
51
52
53
54
55
56
57
58
const currentUserIDState = atom({
key: 'CurrentUserID',
default: null,
});

const userInfoQuery = selectorFamily({
key: 'UserInfoQuery',
get: userID => async () => {
const response = await myDBQuery({userID});
if (response.error) {
throw response.error;
}
return response;
},
});

const currentUserInfoQuery = selector({
key: 'CurrentUserInfoQuery',
get: ({get}) => get(userInfoQuery(get(currentUserIDState))),
});

const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
return friendList.map(friendID => get(userInfoQuery(friendID)));
},
});

function CurrentUserInfo() {
const currentUser = useRecoilValue(currentUserInfoQuery);
const friends = useRecoilValue(friendsInfoQuery);
const setCurrentUserID = useSetRecoilState(currentUserIDState);
return (
<div>
<h1>{currentUser.name}</h1>
<ul>
{friends.map(friend =>
<li key={friend.id} onClick={() => setCurrentUserID(friend.id)}>
{friend.name}
</li>
)}
</ul>
</div>
);
}

function MyApp() {
return (
<RecoilRoot>
<ErrorBoundary>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
</ErrorBoundary>
</RecoilRoot>
);
}

Concurrent Requests 当前请求

If you notice in the above example, the friendsInfoQuery uses a query to get the info for each friend. But, by doing this in a loop they are essentially serialized. If the lookup is fast, maybe that’s ok. If it’s expensive, you can use a concurrency helper such as waitForAll to run them in parallel. This helper accepts both arrays and named objects of dependencies.
如果你注意到上面到例子。friendsInfoQuery发起请求来获取每一个朋友的信息。但是通过这样的循环是为了本质上的有序列化。如果查询速度够快,这没什么,要不然就,可以使用例如waitForAll的并发请求来处理。他既可以接受数组,也可以接受对象作为参数。

1
2
3
4
5
6
7
8
9
10
const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
const friends = get(waitForAll(
friendList.map(friendID => userInfoQuery(friendID))
));
return friends;
},
});

You can use waitForNone to handle incremental updates to the UI with partial data.
同样也可以使用waitForNone部分数据来处理UI层面的更新。

1
2
3
4
5
6
7
8
9
10
11
12
const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
const friendLoadables = get(waitForNone(
friendList.map(friendID => userInfoQuery(friendID))
));
return friendLoadables
.filter(({state}) => state === 'hasValue')
.map(({contents}) => contents);
},
});

Pre-Fetching 预请求

For performance reasons you may wish to kick off fetching before rendering. That way the query can be going while we start rendering. The React docs give some examples. This pattern works with Recoil as well.
由于一些其他的原因,你可能希望能在渲染之前开启请求。这当然也是没有问题的。在开始渲染的时候,是可以发起请求的。React文档给了我们例子,这当然也适用于Recoil.
Let’s change the above example to initiate a fetch for the next user info as soon as the user clicks the button to change users:
我们来修改一下上面的例子,在用户一点击按钮改变用户的时候为下一个用户初始化一个请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function CurrentUserInfo() {
const currentUser = useRecoilValue(currentUserInfoQuery);
const friends = useRecoilValue(friendsInfoQuery);

const changeUser = useRecoilCallback(({snapshot, set}) => userID => {
snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
set(currentUserIDState, userID); // change current user to start new render
});

return (
<div>
<h1>{currentUser.name}</h1>
<ul>
{friends.map(friend =>
<li key={friend.id} onClick={() => changeUser(friend.id)}>
{friend.name}
</li>
)}
</ul>
</div>
);
}

Async Queries Without React Suspense 没有React Suspense的异步请求

It is not necessary to use React Suspense for handling pending asynchronous selectors. You can also use the useRecoilValueLoadable() hook to determine the status during rendering:
使用React Subspense来处理进行中的异步Selector,并不是必须的。在渲染期间,也可以使用useRecoilValueLoadable()钩子函数来处理状态。

1
2
3
4
5
6
7
8
9
10
11
function UserInfo({userID}) {
const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}

Query Refresh请求刷新

When using selectors to model data queries, it’s important to remember that selector evaluation should always provide a consistent value for a given state. Selectors represent state derived from other atom and selector states. Thus, selector evaluation functions should be idempotent for a given input, as it may be cached or executed multiple times. Practically, that means a single selector should not be used for a query where you expect the results to vary during the application’s lifetime.
当使用Selector来发起数据请求的时候,切记,selector运算总是会提供和给定的状态一致的值。Selector代表状态派生到其他的Atom或者是selector状态。因此,对于给定的输入,Selector函数应该是幂等的。因为他可能会被缓存,或者是执行多次。实际上,这意味着一个Selector在整个应用的生命周期中不应该被应用于你希望得到的结果总是变化的请求。

There are a few patterns you can use for working with mutable data:
下面是一些应用于修改数据的模式:

Use a Request ID 用ID请求

Selector evaluation should provide a consistent value for a given state based on input (dependent state or family parameters). So, you could add a request ID as either a family parameter or a dependency to your query. For example:
Selector运算需要为state提供基于输入(独立的状态或者是一组参数)一致的值,所以,需要为请求添加一个请求的ID,要不是参数,要不是依赖,例如:

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
const userInfoQueryRequestIDState = atomFamily({
key: 'UserInfoQueryRequestID',
default: 0,
});

const userInfoQuery = selectorFamily({
key: 'UserInfoQuery',
get: userID => async ({get}) => {
get(userInfoQueryRequestIDState(userID)); // Add request ID as a dependency
const response = await myDBQuery({userID});
if (response.error) {
throw response.error;
}
return response;
},
});

function useRefreshUserInfo(userID) {
setUserInfoQueryRequestID = useSetRecoilState(userInfoQueryRequestIDState(userID));
return () => {
setUserInfoQueryRequestID(requestID => requestID++);
};
}

function CurrentUserInfo() {
const currentUserID = useRecoilValue(currentUserIDState);
const currentUserInfo = userRecoilValue(userInfoQuery(currentUserID));
const refreshUserInfo = useRefreshUserInfo(currentUserID);

return (
<div>
<h1>{currentUser.name}</h1>
<button onClick={refreshUserInfo}>Refresh</button>
</div>
);
}
Use an Atom Atom使用

Another option is to use an atom, instead of a selector, to model the query results. You can imperatively update the atom state with the new query results based on your refresh policy.
另外一个方法就是使用Atom,而不是Selector来处理请求结果。可以根据刷新策略使用新的查询结果强制更新atom状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const userInfoState = atomFamily({
key: 'UserInfo',
default: userID => fetch(userInfoURL(userID)),
});

// React component to refresh query
function RefreshUserInfo({userID}) {
const refreshUserInfo = useRecoilCallback(({set}) => async id => {
const userInfo = await myDBQuery({userID});
set(userInfoState(userID), userInfo);
}, [userID]);

// Refresh user info every second
useEffect(() => {
const intervalID = setInterval(refreshUserInfo, 1000);
return () => clearInterval(intervalID);
}, [refreshUserInfo]);

return null;
}

One downside to this approach is that atoms do not currently support accepting a Promise as the new value in order to automatically take advantage of React Suspense while the query refresh is pending, if that is your desired behavior. However, you could store an object which manually encodes the loading status as well as the results if desired.
这种方法的一个缺点是atoms当前不支持接受Promise作为新值,以便在查询刷新挂起时自动利用React-Suspense(如果这是您想要的行为)。但是,如果需要,您可以存储一个手动编码加载状态和结果的对象。

Also consider atom effects for query synchronization of atoms.
同样需求考虑Atom作用对atom同步请求的影响。

Recoil基础--Selector纯函数

Selectors 纯函数

A selector represents a piece of derived 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.
selecttor就是一种派生状态。你可以认为派生状态是一种高阶函数的返回值。
Derived state is a powerful concept because it lets us build dynamic data that depends on other data. In the context of our todo list application, the following are considered derived state:
派生状态是一个非常有意义的概念,因为他们能让我们创建依赖于其他数据的动态数据。在我们todo list的例子中,下面的数据被视作为派生状态:

  • Filtered todo list: derived from the complete todo list by creating a new list that has certain items filtered out based on some criteria (such as filtering out items that are already completed).
    过滤todo List:
  • Todo list statistics: derived from the complete todo list by calculating useful attributes of the list, such as the total number of items in the list, the number of completed items, and the percentage of items that are completed.
    To implement a filtered todo list, we need to choose a set of filter criteria whose value can be saved in an atom. The filter options we’ll use are: “Show All”, “Show Completed”, and “Show Uncompleted”. The default value will be “Show All”:
    1
    2
    3
    4
    const todoListFilterState = atom({
    key: 'todoListFilterState',
    default: 'Show All',
    });
    Using todoListFilterState and todoListState, we can build a filteredTodoListState selector which derives a filtered list:
    我们可以使用todoListFilterStatetodoListState来创建一个filteredTodoListStateSlector来获取过滤的列表:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const filteredTodoListState = selector({
    key: 'filteredTodoListState',
    get: ({get}) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
    case 'Show Completed':
    return list.filter((item) => item.isComplete);
    case 'Show Uncompleted':
    return list.filter((item) => !item.isComplete);
    default:
    return list;
    }
    },
    });
    The filteredTodoListState internally keeps track of two dependencies: todoListFilterState and todoListState so that it re-runs if either of those change.
    filteredTodoListStatetodoListFilterStatetodoListState有密切关系,只要有其中一个发生变化,就会导致re-runs.

    From a component’s point of view, selectors can be read using the same hooks that are used to read atoms. However it’s important to note that certain hooks only work with writable state (i.e useRecoilState()). All atoms are writable state, but only some selectors are considered writable state (selectors that have both a get and set property). See the Core Concepts page for more information on this topic.

Displaying our filtered todoList is as simple as changing one line in the TodoList component:
展示过滤的todoList列表非常的简单,只需要在TodoList组件中修改一行代码就好。

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

return (
<>
<TodoListStats />
<TodoListFilters />
<TodoItemCreator />

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

Note the UI is showing every todo because todoListFilterState was given a default value of “Show All”. In order to change the filter, we need to implement the TodoListFilters component:
注意,从UI层面来看,展示了所有的item,因为todoListFilterState被赋予了展示所有的默认值。为了过滤,我们需要一个TodoListFilters组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function TodoListFilters() {
const [filter, setFilter] = useRecoilState(todoListFilterState);

const updateFilter = ({target: {value}}) => {
setFilter(value);
};

return (
<>
Filter:
<select value={filter} onChange={updateFilter}>
<option value="Show All">All</option>
<option value="Show Completed">Completed</option>
<option value="Show Uncompleted">Uncompleted</option>
</select>
</>
);
}

With a few lines of code we’ve managed to implement filtering! We’ll use the same concepts to implement the TodoListStats component.
写了这么几行代码,我们已经可以让过滤生效了。我们可以用同样的概念使TodoListStats组件生效。
We want to display the following stats:
我们希望展示以下的统计:

  • Total number of todo items items的总数
  • Total number of completed items 完成状态的item总数
  • Total number of uncompleted items 没有完成状态的item的总数
  • Percentage of items completed 完成状态的item的占比
    While we could create a selector for each of the stats, an easier approach would be to create one selector that returns an object containing the data we need. We’ll call this selector todoListStatsState:
    这时我们可以为每一个统计创建一个Selector.一种最简单的方式就是创建一个selectorr然后返回一个包含我们需要的数据的对象。我们将把这个Selector叫做todoListStatsState:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const todoListStatsState = selector({
    key: 'todoListStatsState',
    get: ({get}) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
    const totalUncompletedNum = totalNum - totalCompletedNum;
    const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;

    return {
    totalNum,
    totalCompletedNum,
    totalUncompletedNum,
    percentCompleted,
    };
    },
    });
    To read the value of todoListStatsState, we use useRecoilValue() once again:
    再用useRecoilValue()函数来读取todoListStatsState的值:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function TodoListStats() {
    const {
    totalNum,
    totalCompletedNum,
    totalUncompletedNum,
    percentCompleted,
    } = useRecoilValue(todoListStatsState);

    const formattedPercentCompleted = Math.round(percentCompleted * 100);

    return (
    <ul>
    <li>Total items: {totalNum}</li>
    <li>Items completed: {totalCompletedNum}</li>
    <li>Items not completed: {totalUncompletedNum}</li>
    <li>Percent completed: {formattedPercentCompleted}</li>
    </ul>
    );
    }
    To summarize, we’ve created a todo list app that meets all of our requirements:
    总结,我们已经创建了满足我们以下需求的todo list应用:
  • Add todo items 增加item
  • Edit todo items 编辑item
  • Delete todo items 删除item
  • Filter todo items 过滤item
  • Display useful stats 展示统计
  • Copyrights © 2015-2022 Lee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信