前言
- 我感觉这个recoil还是挺有前途的,因为redux实际有些不太好的地方,我草稿箱里还有篇讨论redux僵尸children与陈旧props的文章没整理出来。
- 一个库出来我们先学会去使用他,再去搞原理什么的。这玩意有官方光环,必然不是那种随便写写就不维护了或者只是个玩具的东西。
- 它这个库应该是解决redux祖先传来导致更新问题,同时也解决僵尸children与陈旧props的问题,也就是异步刷新取值。本来我也想自己写个类似的东西,既然有官方的就懒得写了。
- https://recoiljs.org/docs/introduction/motivation 在动机这里,recoil官方也指出了这个库解决的问题,这个库也是flow编写的,注释写的倒是挺多,可以学学大神咋写的。
官网
安装
npm install recoil
快速上手
import React from "react";
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from "recoil";
const textState = atom({
key: "textState",
default: "",
});
const charCountState = selector({
key: "charCountState",
get: ({ get }) => {
const text = get(textState);
return text.length;
},
});
function CharacterCounter() {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
}
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
function TextInput() {
const [text, setText] = useRecoilState(textState);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
export default App;
- 我们从useRecoilValue 中通过get取出了文本字符串,返回的对象即为取出的对象。
- 而状态定义使用useRecoilState而非useState或者直接写入store。
- 这里可以看见定义值和取值都要整个key做唯一Id,官网介绍说一些高级的功能需要使用这个。
- 前面那个没有在同一个组件取值,同一组件取值设值是没有问题的:
const fontSizeState = atom({
key: "fontSizeState",
default: 14,
});
const fontSizeLabelState = selector({
key: "fontSizeLabelState",
get: ({ get }) => {
const fontSize = get(fontSizeState);
const unit = "px";
return `${fontSize}${unit}`;
},
});
function FontButton() {
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<>
<div>Current font size: ${fontSizeLabel}</div>
<button
onClick={() => setFontSize(fontSize + 1)}
style={{ fontSize }}
>
Click to Enlarge
</button>
</>
);
}
- hooks的位置对掉也不会影响输出,它可以确保拿到的始终是最新值,并且组件只刷新了一次。
API
<RecoilRoot …props />
- 这个就是上下文,应该不用介绍都懂。
- 它有个类似redux的可选初始值,用于设定初始值,不像redux的store要先把reducer甩进去,这个不传可以异步进行设定,可以说相当强大。
initializeState?: (MutableSnapshot => void)
State
atom(options)
function atom<T>({
key: string,
default: T | Promise<T> | RecoilValue<T>,
effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
- recoilvalue是
RecoilValue<T> = RecoilValueReadOnly<T> | RecoilState<T>;
而这2实际是:
declare class AbstractRecoilValue<T> {
__tag: [T];
__cTag: (t: T) => void;
key: NodeKey;
constructor(newKey: NodeKey);
}
declare class AbstractRecoilValueReadonly<T> {
__tag: [T];
key: NodeKey;
constructor(newKey: NodeKey);
}
- effects_UNSTABLE是一个实验性功能,感觉这个功能需要解决最大问题就是无限循环问题。由于是个实验性功能,暂不介绍也不使用它。
- dangerouslyAllowMutability 这个东西我也是花了点时间搞明白了。里面涉及到一点它们的处理,他们对对象做了深冻结,所以如果你定义的是对象,那么你无法直接在对象上进行修改,因为react是可不可变的值,你如果做了修改,那么就不符合这个原则。但有时我可能要这么做:
const userState = atom<{ xx: string } | null>({
key: "userState",
default: null,
dangerouslyAllowMutability: false,
});
function UserComp() {
const [user, setUser] = useRecoilState(userState);
return (
<>
<div> : {user && user.xx}</div>
<button
onClick={() => {
setTimeout(() => {
setUser((pre) => {
console.log(pre);
if (pre) {
pre.xx = pre.xx + "1";
return { ...pre };
} else {
return { xx: "111" };
}
});
}, 1000);
}}
>
Click to Enlarge
</button>
</>
);
}
const getUser = selector({
key: "getUser",
get: ({ get }) => {
const user = get(userState);
return user?.xx;
},
});
function Parent() {
return (
<div>
<UserComp></UserComp>
<Children></Children>
</div>
);
}
function Children() {
const user = useRecoilValue(getUser);
return <div>{user}</div>;
}
Cannot assign to read only property 'xx' of object '#<Object>'
- 这个就是冻结生效,所以会有这个选项, 当写成true后,你便可以这么操作。
- 如果你错误的返回了pre:
if (pre) {
pre.xx = pre.xx + "1";
return pre;
} else {
return { xx: "111" };
}
- 组件将不会更新。所以这个也是有意在引导用户正确的使用react。
- 而为啥叫atom,我感觉也是有意让用户使用更基础的变量而非对象。这样在性能上比使用对象会有更大提升。这样粒度更细就不会有啥无用更新。
- 所以,对于组件内使用,atom定义完后使用自定义hook,也分为了好几种:
useRecoilState():打算同时读取和写入atom时,请使用此钩子。这个钩子使组件订阅atom。
useRecoilValue():仅打算读取atom时,请使用此钩子。这个钩子将组件订阅到atom上。
useSetRecoilState():仅打算写入atom时,请使用此钩子。
useResetRecoilState():使用此钩子将atom重置为其默认值。
selector(options)
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,
})
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);
}
},
});