荣耀手机官方网站,网页制作与设计的内容,小米网站建设,石家庄代理注册公司Redux 太繁琐#xff0c;Mbox 很酷但我们可能没必要引入新的包#xff0c;那就让我们亲自在 react.js 中通过代理实现一套钩子来达到类似 vue 的响应式状态#xff1a;
实现 reactive hooks
代理类声明
代理状态的类应当提供可访问的状态#xff0c;和订阅变化的接口。 …Redux 太繁琐Mbox 很酷但我们可能没必要引入新的包那就让我们亲自在 react.js 中通过代理实现一套钩子来达到类似 vue 的响应式状态
实现 reactive hooks
代理类声明
代理状态的类应当提供可访问的状态和订阅变化的接口。
export type ListenerT (state: T) any;export interface ReactiveCtxModelT any {value: T;subscribe(listener: ListenerT): () void;
}代理类实现
使用 es6 class 来实现代理类es6 class 提供了属性的 get/set 访问器让我们在通过 obj.key 的方式访问时不是直接访问而是经过了代理真实的值则通过 private 被设置为私有属性。
类的构造器我们传入用 React.useState 获得的返回没错想在 react 中让页面响应数据的变化我们仍然需要 useState不传 setState 的话这个 Reactive 将是惰性的因为他无法触发页面的重渲染。
私有属性除了要保存的 state还有 listeners 数组来保存监听变化要触发的函数这些函数在 state 每次被 set 访问器调用时跟着调用。
export class ReactiveT any implements ReactiveCtxModel {private _state: T;private _setState: any (newState: T) {this._state newState;};private _listeners: ListenerReadonlyT[] [];constructor(state: T, setState?: any) {this._state state;setState ? (this._setState setState) : void 0;}get value(): T {return this._state;}set value(newState: T) {this._setState?.(newState);this._listeners.forEach((listener) listener(newState));}subscribe(listener: ListenerT) {this._listeners.push(listener);return () {this._listeners this._listeners.filter((l) l ! listener);};}static isReactive(obj: any) {return Reactive.prototype.isPrototypeOf(obj);}
}实现创建代理的钩子函数
每次在代码里手动创建 useState() 然后还要 new Reactive() 太麻烦了我们将这几个操作封装成一个 hook Reactify然后再赋给 reactive这样我们就可以直接使用 reactive(initialValue) 创建响应式对象。为什么要先创建 Reactify因为 react 约定 react 的 use 钩子的顶部空间应当命名为 useXXX 或者是 大写字母 开头因为我喜欢 reactive 这个名字所以做了一个交换
const Reactify T any(initialValue: T): ReactiveT {const [state, setState] React.useStateT(initialValue);const observer new Reactive(state, setState);return observer;
};
/*** reactive is same with Reactify*/
export const reactive Reactify;example
const Demo: React.FC () {let state reactive(0);const num state.value;return (ButtononClick{() {state.value state.value 1;}}{num}/Button/);
};实现监听函数
直接在 Reactive 对象上调用 subscribe 很棒但有时候我更喜欢这个操作可以抽出来于是有了下面这个 listen 函数传入要监听的 Reactive 对象接着在 then 中链式传入要触发的回调观感上更优雅。
/*** When store.state changes, call the given function.* param target listened Reactive store* returns unlistener*/
export function listenT any(target: OmitReactiveT, _state | _setState) {return {then: (...fns: ((value: T) any)[]) {const fn (value: T) fns.forEach((f) f(value));const dispose target.subscribe(fn);return dispose;},};
}example listen(obj).then((newVal) {console.log(newVal: ${newVal});});借助 Context 传递 Reactive
以上的 reactive 只能在单组件局部使用即使通过 props 传递给子组件子组件也只有只读的权利。如果需要跨组件共享 Reactive 代理我们可以借助 React.Context
创建默认 Context
import { createContext } from react;
import { Listener, Reactive } from ./model;export const createReactiveContext T any(initialValue?: T) {const reactiveObject new Reactive(initialValue);return createContextReactiveCtxModelT | undefined(reactiveObject as any);
};const ReactiveCtx createReactiveContext();export default ReactiveCtx;实现 useReactive 钩子
useReactive 可以接收一个初值如果得到了初值就开辟一个新的 context 和 Reactive 对象否则延用上一步创建的 ReactiveCtx。
/*** Accept a value and return a reactive object. When initalValue is valid a new reactive object will be created.*/
export const useReactive T any(initialValue?: T): ReactiveT {const [state, setState] React.useStateT(initialValue ?? (undefined as T));const reactiveObj new Reactive(state, setState);const defaultContextModel React.useContext((initialValue as any) ?? ReactiveCtx);if (initialValue ! undefined initialValue ! null) {return reactiveObj as ReactiveT;}return defaultContextModel as ReactiveT;
};实现 useReactiveContext 钩子
useReactive 接收初值后新建的 context 不能为其它组件获取要让其它组件共享非默认的 context我们就需要在外部额外创建并导出新的 context并实现一个 useReactiveContext 钩子来接收新的context这样就可以共享新的 context同样如果没有传入新的 context我们将沿用默认的 ReactiveCtx。
export const useReativeContext T any(context?: React.ContextReactiveCtxModelT | undefined): ReactiveT {const reactiveCtxModel React.useContext(context || ReactiveCtx);return reactiveCtxModel as ReactiveT;
};现在我们将原先 demo 中使用的 raective 替换为 useReactive然后我们即可自由的跨组件共享 Reactive。 example
const Demo: React.FC () {let state useReactive(0);const num state.value;listen(state).then((newVal) {console(newVal: ${newVal});});return (Button$click{() {state.value state.value 1;}}{num}/ButtonReactiveCtx.Provider value{state}Kid //ReactiveCtx.Provider/);
};Kid
function Kid() {const state useReactivenumber();return (Taglightstyle{{ cursor: pointer }}onClick{() {state.value;}}state : {state.value}/TagTaglightstyle{{ cursor: pointer }}onClick{() {state2.value;}}state2 : {state2.value}/Tagcontext.Provider value{state2}KidKid //context.Provider/);
}Bingo! 到这里我们就基本实现了 reactive 啦拜拜~