[FE] Hello "Observable Hooks"
最近项目中用到了 react + rxjs + observable-hooks,下文总结一下 observable-hooks 的学习心得。
1. 搭建 React 环境
使用 create-react-app 创建 react 项目 test-hooks
,
$ npm i -g create-react-app
$ create-react-app test-hooks
$ cd test-hooks
$ npm i
$ npm start
我们可以在 src/App.js 中写一些实验代码,例如,
function App() {
return (
<>Hello World</>
);
}
export default App;
2. Observable Hooks
Observable Hooks 是 React hooks for RxJS Observables
,
官方文档 Core Concepts 中介绍了 Two Worlds 概念模型,非常值得一读。
为了使用 Observable Hooks,需要在上文 test-hooks
项目中,
安装 rxjs
和 observable-hooks
依赖。
$ npm i -S rxjs observable-hooks
(1)异步取值
功能:页面初始化展示 0
,500ms 后展示 1
。
import { from } from 'rxjs';
import { useObservableState } from 'observable-hooks';
function App() {
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
const initialOutput = 0;
const output = useObservableState(output$, initialOutput);
return (
<>{output}</>
);
}
export default App;
-
from
将 Promise 转换成了一个 rxjs 流(stream)output$
-
useObservableState
会订阅(subscribe)这个流,流中每一个值 “到来” 的时候,更新output
值 -
output
的初始值为initialOutput
,总是先于流中的第一个值被赋值
这种写法非常适合处理异步加载页面的场景:
首屏加载后,前端通过 ajax 请求获取数据,更新页面。
(2)事件更新
功能:页面展示一个按钮和数字 0
,每次点击按钮,后面的数字加一。
import { useObservableState } from 'observable-hooks';
function App() {
const initialOutput = 0;
const transform = output$ => output$.pipe(
//
);
const [output, onInput] = useObservableState(transform, initialOutput);
const onClick = () => {
onInput(output + 1);
};
return (
<>
<input type="button" value="Add One" onClick={onClick} />
{output}
</>
);
}
export default App;
-
output
的初始值被设置为initialOutput
- 每次调用
onInput
就会向output$
流中加入一个新值 -
transform
是流变换函数,可以用 rxjspipe
+ operators 来实现 -
useObservableState
会订阅transform
过后的新流,来对output
重新赋值
每次
onInput
加入流中的值,
通过transform
,再经过useObservableState
,最后赋值给了output
。
(3)异步回调
功能:页面加载 500ms 后,写 log
import { from } from 'rxjs';
import { useSubscription } from 'observable-hooks';
function App() {
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
useSubscription(output$, v => {
console.log(v);
});
return (
<>Hello World</>
);
}
export default App;
-
from
会将 Promise 转换成一个流output$
- 流中每一个值 “到来” 的时候,触发回调
-
useSubscription
可以多次使用,流中的每一个值会触发多次回调
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
useSubscription(output$, v => {
console.log(v); // 1
});
useSubscription(output$, v => {
console.log(v); // 1
});
实际上,上文介绍的 useObservableState
与 useSubscription
对于同一个流也是可以多次使用的。
import { from } from 'rxjs';
import { useObservableState, useSubscription } from 'observable-hooks';
function App() {
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
const initialOutput = 0;
const output = useObservableState(output$, initialOutput);
debugger; // 1 [先触发,跟先后顺序有关]
useSubscription(output$, v => {
debugger; // 1 [后触发,跟先后顺序有关,放在前面就会先触发]
});
return (
<>Hello World</>
);
}
export default App;
(4)变量写入流
功能:
页面展示一个按钮和数字 0
,每次点击按钮,后面的数字加一。
按钮点击会改变组件状态,组件状态变更,会导致流中加入新值。
import { useState } from 'react';
import { useObservableState, useObservable } from 'observable-hooks';
function App() {
const [state, setState] = useState(0);
const transform = output$ => output$.pipe(
//
);
const output$ = useObservable( // 这里是关键
transform,
[state]
);
const initialOutput = state;
const output = useObservableState(output$, initialOutput);
const onClick = () => {
setState(state + 1);
};
return (
<>
<input type="button" value="Add One" onClick={onClick} />
{output}
</>
);
}
export default App;
-
useObservable
的第二个参数,传入了output$
依赖的变量列表[state]
,
当state
值改变时,就会向流中加入这个值,随后transform
对流进行了变换 - 组件通过
setState
更新后output$
并没有变(是同一个对象),改变的只是流中的值
(5)事件写入流
功能:
页面展示一个按钮和数字 0
,每次点击按钮,后面的数字加一。
按钮点击事件,会直接在流中加入新值。
import { useObservableState, useObservableCallback } from 'observable-hooks';
function App() {
const transform = output$ => output$.pipe(
//
);
const [onInput, output$] = useObservableCallback( // 这里是关键
transform
)
const initialOutput = 0;
const output = useObservableState(output$, initialOutput);
const onClick = () => {
onInput(output + 1);
};
return (
<>
<input type="button" value="Add One" onClick={onClick} />
{output}
</>
);
}
export default App;
-
useObservableCallback
会返回一个函数onInput
和一个流output$
,调用这个函数onInput
会向output$
中加入新值
(6)创建常量流
上文提到了 from
将 Promise 转换成流的做法,每次调用 from
都会产生新的流。
尤其是当组件通过 setState
状态更新的时候。
而使用下述写法,可以创建一个在组件更新时不变的流 output$
,
每次 useObservable
都返回相同的对象。
import { useState } from 'react';
import { from } from 'rxjs';
import { useObservable } from 'observable-hooks';
let a;
function App() {
const [state, setState] = useState(0);
const output$ = useObservable(
() => from(new Promise(res => setTimeout(() => res(1), 500))),
);
const l = a === output$;
debugger; // 组件更新时 l === true
a = output$; // 保存上一次的值
const onClick = () => {
setState(state + 1);
};
return (
<input type="button" value="Add One" onClick={onClick} />
);
}
export default App;
3. 小结
- 上文总共介绍了 4 个 hooks,用于了 6 个场景
// 异步取值 [流 -> 值]
const output = useObservableState(output$, initialOutput);
// 事件更新 [流变换 -> 值, 写入流]
const [output, onInput] = useObservableState(transform, initialOutput);
// 异步回调 [流, 回调 -> void]
useSubscription(output$, v => {
console.log(v);
});
// 变量写入流 [流变换, 变量依赖 -> 常量流]
const output$ = useObservable(
transform,
[state],
);
// 事件写入流 [流变换 -> 写入流, 常量流]
const [onInput, output$] = useObservableCallback(
transform,
);
// 创建同一个流 [流变换 -> 常量流]
const output$ = useObservable(
() => from(new Promise(res => setTimeout(() => res(1), 500))),
);
- 以上 hooks 都是建立在 “常量流” 基础之上的,即组件更新后
$output
并没有变,只是流中的值发生了变化。
import { useState } from 'react';
import { from } from 'rxjs';
import { useObservableState } from 'observable-hooks';
function App() {
const [state, setState] = useState(0);
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
const output = useObservableState(output$, 0); // output$ 在组件更新后重置,所以 output 总是为 0
const onClick = () => {
setState(state + 1);
};
return (
<>
{output}
<input type="button" value="Add One" onClick={onClick} />
</>
);
}
export default App;
- 使用 Observable Hooks 时,我是这么考虑的:
- 我们需要一个 “常量流”,在组件更新时不变(里面的值会变)
- 依赖的变量改变了、或事件主动触发,都可以在流中添加值
- 流中有了新值,会重新触发流变换函数
transform
- 使用
useObservableState
从流中取值来用