background
Recently, when studying the source code of zustand, the state manager of react, it was found that its component registration binding was updated through observer mode combined with react hooks. When Lenovo wrote vue before, it often used the built-in user-defined events of vue for component communication ($emit/on). This should be the publish and subscribe mode, which made me nod. I felt that the two modes were very similar, and I was a little confused and didn't understand them thoroughly. Therefore, this time, I took the opportunity to study these two modes in depth, Try writing by yourself to deepen your understanding. This article is my personal experience. If there are mistakes, please correct them and make common progress~
contrast
difference
Observer mode: in software design, it is an object that maintains a dependency list and automatically notifies them when any state changes.
Publish subscribe design pattern: the sender (publisher) of a message will not directly send it to a specific receiver (called a subscriber), but will filter and allocate messages through an information intermediary.
The popular image is:
- In the observer mode, no middlemen earn the price difference, while in the publish and subscribe mode, middlemen earn the price difference.
- The observer mode is a one size fits all mode, which treats all subscribers equally. The publish and subscribe mode can wear colored glasses and have a layer of filtering or black box operation.
Post a picture and let's feel it
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-qqz66s20-1631541451800)( https://user-gold-cdn.xitu.io/2017/11/22/15fe1b1f174cd376?imageView2/0/w/1280/h/960/format/webp/ignore -error/1)]
To sum up
-
In the observer mode, the observer knows the Subject, and the Subject keeps a record of the observer. However, in the publish subscribe mode, the publisher and subscriber do not know the existence of each other. They communicate only through the message broker.
-
In publish subscribe mode, components are loosely coupled, as opposed to observer mode.
-
The observer mode is synchronized most of the time. For example, when an event is triggered, the Subject will call the observer's method. The publish subscribe mode is asynchronous most of the time (using message queuing).
-
The observer pattern needs to be implemented in a single application address space, while publish subscribe is more like a cross application pattern.
The concepts seem quite clear, and the differences between them are easy to understand. Next, we begin to implement it ourselves and go deep into its internal principle and operation logic.
Publish subscribe mode
vue custom event Event Bus is the implementation of publish subscribe mode and the Emitter Event of Nodejs.
Implement a publication subscription that supports subscription, unbinding, publishing, and multiple binding of events of the same type.
Let's have a simple implementation
Upper code
// Subscription Center const subscribers = {} // subscribe const subscribe = (type, fn) => { // Add a queue in array mode to support multiple bindings of the same type if (!subscribers[type]) subscribers[type] = [] subscribers[type].push(fn) } // release const publish = (type, ...args) => { if (!subscribers[type] || !subscribers[type].length) return subscribers[type].forEach((fn) => fn(...args)) } // Unbind subscription const unsubscribe = (type, fn) => { if (!subscribers[type] || !subscribers[type].length) return subscribers[type] = subscribers[type].filter((n) => n !== fn) }
Verification test
// console test ======> subscribe("topic-1", () => console.log("suber-A Subscribed topic-1")) subscribe("topic-2", () => console.log("suber-B Subscribed topic-2")) subscribe("topic-1", () => console.log("suber-C Subscribed topic-1")) publish("topic-1") // Notify A and C who have subscribed to topic-1 // Output results // suber-A subscribes to topic-1 // suber-C subscribed to topic-1
Implement an Emitter class
Upper code
class Emitter { constructor() { // Subscription Center this._event = this._event || {} } // Register Subscriber addEventListener(type, fn) { const handler = this._event[type] if (!handler) { this._event[type] = [fn] } else { handler.push(fn) } } // Uninstall subscription removeEventListener(type, fn) { const handler = this._event[type] if (handler && handler.length) { this._event[type] = handler.filter((n) => n !== fn) } } // notice emit(type, ...args) { const handler = this._event[type] if (handler && handler.length) { handler.forEach((fn) => fn.apply(this, args)) } } }
Verification test
// console test ======> const emitter = new Emitter() emitter.addEventListener("change", (obj) => console.log(`name is ${obj.name}`)) emitter.addEventListener("change", (obj) => console.log(`age is ${obj.age}`)) const sex = (obj) => console.log(`sex is ${obj.sex}`) emitter.addEventListener("change", sex) emitter.emit("change", { name: "xiaoming", age: 28, sex: "male" }) console.log("event-A", emitter._event) emitter.removeEventListener("change", sex) console.log("====>>>>") emitter.emit("change", { name: "xiaoming", age: 28, sex: "male" }) console.log("event-B", emitter._event) // output // name is xiaoming // age is 28 // sex is male // event-A {change: Array(3)} // ====>>>> // name is xiaoming // age is 28 // event-B {change: Array(2)}
vue Event Bus implementation
Structure combing
Source location: src/core/instance/events.js
First, we analyze the structure according to the source code and sort out the event implementation logic of vue
-
Put the event center_ Mount events to Vue instance:
vm._events = {}
-
Mount all methods: $on, $once, $off, $emit to the Vue prototype
The advantage of this is that this.$on and this.$emit can be used directly in Vue components
// $on Vue.prototype.$on = function(){} // $once Vue.prototype.$once = function(){} // $once Vue.prototype.$off = function(){} // $once Vue.prototype.$emit = function(){}
Look at the code
-
$on add registration
// $on Vue.prototype.$on = function (event, fn) { const vm = this // If the type of the event listening event passed in is array, call the $on method recursively if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn) } } else { // If there is a direct add, there is no add after new ;(vm._events[event] || (vm._events[event] = [])).push(fn) } // Returns this for chained calls return vm }
-
$once single execution
// $once Vue.prototype.$once = function (event, fn) { const vm = this // When the event event is triggered, the on method is called function on() { // First, execute the $off method to unload the callback method vm.$off(event, on) // Then execute this callback method fn.apply(vm, arguments) } // This assignment will be used in $off: cb.fn === fn // Because the $once method calls the $on callback, but the wrapped on method is added instead of the fn method // Therefore, when we call the $off method alone to delete the fn callback, we cannot find it. In this case, we can judge by cb.fn === fn on.fn = fn // Call the $on method to add the callback to the queue vm.$on(event, on) return vm }
-
$off uninstall delete
// $off Vue.prototype.$off = function (event, fn) { const vm = this // If no parameters are passed in, all events will be cleared if (!arguments.length) { vm._events = Object.create(null) return vm } // If the event is an array, it is the same as the $on logic, and the event is unloaded recursively if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } // callback list const cbs = vm._events[event] // If there is no binding callback for this event, it will not be processed if (!cbs) { return vm } // If the unbinding callback of the corresponding event is not passed in, all the events of the event will be cleared if (!fn) { vm._events[event] = null return vm } // Event event type and callback exist. Traverse to find and delete the specified callback let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } return vm }
-
$emit trigger event
toArray method// $emit Vue.prototype.$emit = function (event) { const vm = this // callback list let cbs = vm._events[event] // Judge whether there is an execution callback for the event if (cbs) { // The $emit method can pass parameters, which will be passed in when calling the callback function // Exclude other parameters of the event parameter // toArray is a method that converts a class array into an array and supports interception const args = toArray(arguments, 1) // Traversal callback function for (let i = 0, l = cbs.length; i < l; i++) { cbs[i].apply(vm, args) } } return vm }
// Convert an Array-like object to a real Array. function toArray (list, start) { start = start || 0; var i = list.length - start; var ret = new Array(i); while (i--) { ret[i] = list[i + start]; } return ret }
Under test
Let's simulate a Vue class to test
class Vue { constructor() { this._events = {} } // Provide an external access_ events interface get event() { return this._events } }
Verify the results
// instantiation const myVue = new Vue() // Add subscription const update_user = (args) => console.log("user: ", args) const once_update_user = (args) => console.log("once_user: ", args) myVue.$on("user", update_user) myVue.$once("user", once_update_user) // The subscription is automatically uninstalled when triggered // Output printing console.log("events: ", myVue.event) // events: {user: [(args) => console.log("user: ", args), ƒ on()]} // Trigger notification myVue.$emit("user", { name: "xiaoming", age: 18 }) console.log("events: ", myVue.event) // events: {user: [(args) => console.log("user: ", args)]} // user: {name: "xiaoming", age: 18} // once_user: {name: "xiaoming", age: 18} // Uninstall subscription myVue.$off("user", once_update_user) console.log("events: ", myVue.event) // events: {user: []}
Little summary
The publish subscribe mode encapsulated by Vue can be said to be very perfect. It is a code that can be extracted independently and used in other projects, and then adjust the location of the event memory according to its own needs (Vue is placed on the instance).
From the simplest lines of code to the detailed and complete implementation in the framework, we can find that as long as we have the right idea and master and understand the core methods, we can easily understand the implementation principle, and most of the rest are the judgment and handling of various abnormal situations.
Observer mode
As long as the state of an object changes, all objects that depend on it are notified and updated automatically.
Also a simple implementation
// Observer list const observers = [] // add to const addob = (ober) => { observers.push(ober) } // notice const notify = (...args) => { observers.forEach((fn) => fn(args)) } // Test ========> const subA = () => console.log("I am sub A") const subB = (args) => console.log("I am sub B", args) addob(subA) addob(subB) notify({ name: "sss", site: "ssscode.com" }) // I am sub A // I am sub B [{name: "sss", site: "ssscode.com"}]
Implement an observer class
Upper code
// Observer class Observer { constructor(name) { // Observer name this.name = name } // trigger update() { console.log("Observer:", this.name) } } // Observed class Subject { constructor() { // Observer list this._observers = [] } // Get observer list get obsers() { return this._observers } // add to add(obser) { this._observers.push(obser) } // remove remove(obser) { this._observers = this._observers.filter((n) => n !== obser) } // Notify all observers notify() { this._observers.forEach((obser) => obser.update()) } }
Verification test results
// Observer const obserA = new Observer("obser-A") const obserB = new Observer("obser-B") // Observed const subject = new Subject() // Add to observer list subject.add(obserA) subject.add(obserB) // notice subject.notify() console.log("Observer list:", subject.obsers) // Observer: observer-a // Observer: observer-b // Observer list: (2) [Observer, Observer] // remove subject.remove(obserA) // notice subject.notify() console.log("Observer list:", subject.obsers) // Observer: observer-b // Observer list: [observer]
Vue bidirectional data binding
The bidirectional data binding of vue is the implementation of observer mode.
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-nwg3m622-1631541451802)( https://user-gold-cdn.xitu.io/2018/10/23/166a031209fc8da5?imageView2/0/w/1280/h/960/format/webp/ignore -error/1)]
Use Object.defineProperty() to hijack data and set up a listener Observer to listen to all properties. If the properties change, you need to tell the subscriber Watcher to update the data. Finally, the instruction parser Compile parses the corresponding instructions and executes the corresponding update function to update the view and realize two-way binding~
The core of vue2.x is to hijack the data through the Object.defineProperty() method, redefine the set and get methods, and update the view once the data changes.
Vue will have a dependency collection process during initialization. Through the traversal process of attributes and instructions (the attributes here include props, data, etc., and the instructions are filtered through compile compilation), Vue will get the attributes that need to be processed responsively, and then realize monitoring, dependency collection and subscription through Observer, Dep and Watcher.
Interested friends can try to download the source code, and then use the browser breakpoint debugging to see the whole initialization process of Vue, which is very helpful for everyone to understand the running logic and process of Vue.
Here, let's take a brief look at Vue's processing of data. Other rendering passes will not be analyzed.
Initialize initData
// The $options here is actually when we write Vue // props, data, method, computed and other attributes. function initData(vm) { // data processing, function / object let data = vm.$options.data // Why do you suggest that data in vue be written in functional form? // When a component is defined, data must be declared as a function that returns an initial data object, because the component may be used to create multiple instances // If data is still a pure object, all instances will share and reference the same data object // By providing the data function, we can call the data function every time a new instance is created, // This returns a new copy of the original data object. // (js directly assigns the same memory address when assigning the object object. Therefore, this method is adopted for the data independence of each component.) data = vm._data = typeof data === "function" ? getData(data, vm) : data || {} // observe data observe(data, true /* asRootData */) }
Create observe r
function observe(value, asRootData) { let ob // Observer ob = new Observer(value) // asRootData = true if (asRootData && ob) { ob.vmCount++ } // return ob }
Observer class observer
class Observer { constructor(value) { this.value = value this.dep = new Dep() this.vmCount = 0 if (Array.isArray(value)) { this.observeArray(value) } else { this.walk(value) } } // Process all attributes and perform responsive processing walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } // Array traversal processing observeArray(items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
Data hijacking, packing set method, monitoring data update, defineReactive
Because Object.defineProperty cannot listen to array subscripts, Vue actually rewrites the original methods of the array, such as push and pop. First, execute the original logic function. If you add elements to the array, the new elements will become responsive.
function defineReactive(obj, key, val) { // Dependency collection const dep = new Dep() // Data hijacking, wrapper set method, add notify notification Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { // If the watcher exists, the dependency collection is triggered if (Dep.target) { dep.depend() } return val }, set: function reactiveSetter(newVal) { // ... // Data change = = > trigger set method = = > call dep.notify() to notify update dep.notify() }, }) }
Dependency collection class Dep
class Dep { constructor() { this.id = uid++ this.subs = [] // Used to store subscriber Watcher } addSub(sub) { // sub ===> Watcher // This method is executed when the watcher adds a subscription this.subs.push(sub) } removeSub(sub) { // sub ===> Watcher remove(this.subs, sub) } // Dep.target===watcher, i.e. watcher.addDep depend () { if (Dep.target) { Dep.target.addDep(this) } } notify() { // subs const subs = this.subs.slice() // Call the update of the watcher for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // Store unique watcher Dep.target = null const targetStack = [] function pushTarget (target) { targetStack.push(target) Dep.target = target } function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
Subscriber watcher
// Deleted part, just look at the core code class Watcher { constructor(vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm vm._watchers.push(this) this.cb = cb this.deps = [] this.newDeps = [] this.value = this.get() this.getter = expOrFn } // Get the latest value and collect dependencies get() { pushTarget(this) let value const vm = this.vm value = this.getter.call(vm, vm) if (this.deep) { // Collect each dependency of nested attributes traverse(value) } popTarget() this.cleanupDeps() return value } // Add dependency // dep === class Dep addDep(dep) { this.newDeps.push(dep) dep.addSub(this) } // Clear dependency cleanupDeps() { let i = this.deps.length while (i--) { const dep = this.deps[i] dep.removeSub(this) } this.deps = this.newDeps } // Provide updated interfaces update() { this.run() } // Notification execution update run() { const value = this.get() this.cb.call(this.vm, value, oldValue) } // Collect all dependencies through the watcher depend() { let i = this.deps.length while (i--) { this.deps[i].depend() } } }
Through the above code, we can find that Vue adds subscription listening to each attribute in the whole data object during initialization, and rewrites the set so that we can trigger a notification when modifying the data, so that all the added subscribed attributes can be updated, Then, the view layer can be updated by render ing in combination with Vue's compiler compilation.
At this stage, we can notify and update the data, but we all know that vue is two-way data binding. When the data changes, we will continue to notify the view to update. That is, when the template compiler complie, it will filter the instructions (v-bind, v-mode, etc.) and add Watcher subscription to realize the binding and communication between observe < = = = > Watcher < = = > complie.
watcher source code: https://github1s.com/vuejs/vue/blob/HEAD/src/core/observer/watcher.js
I won't go further here. I feel a little indescribable 🤣, It's really a big head. It involves a lot of content. If you have the opportunity, you can read the source code series..., It's a little far away. Back to the article, we mainly throw out the use scenario of observer mode, and discuss the initialization process of Vue and the principle of two-way binding~
Interested students can read this article: Observer mode to achieve vue bidirectional data binding
zustand status manager
Let's look at the usage first
Create store
// store import create from 'zustand' // create a responsive store through the create method const useStore = create(set => ({ bears: 0, increasePopulation: () => set(state => ({ bears: state.bears + 1 })), // Function writing removeAllBears: () => set({ bears: 0 }) // Object writing }))
Component reference
// The UI component displays the bearings status. When the status changes, the component can be updated synchronously function BearCounter() { const bears = useStore(state => state.bears) return <h1>{bears} around here ...</h1> } // The control component executes the click event through the increasePopulation method created in the store to trigger the update of data and UI components function Controls() { const increasePopulation = useStore(state => state.increasePopulation) return <button onClick={increasePopulation}>one up</button> }
Combined with the official example, it can be determined that zustand adds and registers the components bound through state to the subscriber queue by default. At this time, the bearings attribute is equivalent to an observer. When the bearings status changes, all components subscribed to the attribute will be notified to update. (we can roughly speculate about this set method)
No more nonsense. Looking at the code, let's first analyze according to the logic of creating the store:
That is, create accepts a function (non function cases will not be studied temporarily) and returns the state and method defined by us. The function provides a set method for us to use, and this set method must be able to trigger the update notification.
Direct code
- create method
function create(createState) { // Initialization processing createState const api = typeof createState === "function" ? createImpl(createState) : createState }
- A createImpl method is introduced here. Let's take a look at the processing and return value of createState.
function createImpl(createState) { // Used to cache the last state let state // listen queue const listeners = new Set() const setState = (partial, replace) => { // If it is a function, inject state and obtain the execution result; otherwise, take the value directly // For example: setcount: () = > set (state = > ({state: state. Count + 1}) // For example: setcount: () = > set ({count: 10}) const nextState = typeof partial === "function" ? partial(state) : partial // Optimization: judge whether the status has changed, and then update the component status if (nextState !== state) { // Last status const previousState = state // Current status latest status state = replace ? nextState : Object.assign({}, state, nextState) // Each component in the notification queue listeners.forEach((listener) => listener(state, previousState)) } } // Function to get state const getState = () => state // The subscription method is processed when there is a selector or equalityFn parameter const subscribeWithSelector = (listener, selector = getState, equalityFn = Object.is) => { // Current value let currentSlice = selector(state) // The listener toadd method is actually added to the queue, function listenerToAdd() { // The value when the subscription notification is executed, that is, the value of the next update const nextSlice = selector(state) // If the values before and after comparison are not equal, an update notification is triggered if (!equalityFn(currentSlice, nextSlice)) { // Last value const previousSlice = currentSlice // Execute the added subscription function // For example: usestore. Subscribe (console. Log, state = > state. Paw) // console.log in listener((currentSlice = nextSlice), previousSlice) } } // add listenerToAdd listeners.add(listenerToAdd) // Unsubscribe return () => listeners.delete(listenerToAdd) } // Add subscription // Columns such as usestore.subscribe (console.log, state = > state. Paw) // Effect: only listen for changes in paw and notify updates const subscribe = (listener, selector, equalityFn) => { // If the selector or equalityFn parameter exists, follow this logic to add the specified subscription notification if (selector || equalityFn) { return subscribeWithSelector(listener, selector, equalityFn) } // Otherwise, add subscription notifications to all changes listeners.add(listener) // Unsubscribe // The execution result is to delete the subscriber function // Namely: const unsubscribe = subscribe() = () = > listeners.delete (listener) return () => listeners.delete(listener) } // Clear subscription const destroy = () => listeners.clear() // The processing result returned to the create method, that is, four processing methods are returned const api = { setState, getState, subscribe, destroy } // It injects three parameters setstate, getstate and API into the passed createState function // When creating a store in create, you can use methods in the parameters of the callback function to process the data // For example: create (set = > ({count: 0, setcount: () = > set (state = > ({state: state. Count + 1}))})) // Call and return API = {setstate, getstate, subscribe, destroy} attribute method state = createState(setState, getState, api) return api }
You can get the execution result of createImpl
const api = { setState, getState, subscribe, destroy }
Then we'll come back and continue to analyze the create method
-
Briefly introduce the difference between useeffect / uselayouteeffect used in the code
- useEffect is executed asynchronously, while uselayouteeffect is executed synchronously.
- The execution time of useEffect is after the browser finishes rendering, while the execution time of uselayouteeffect is before the browser actually renders the content to the interface, which is equivalent to componentDidMount.
-
create method
import { useReducer, useLayoutEffect, useRef } from "react" // Is it a non browser environment const isSSR = typeof window === "undefined" || !window.navigator || /ServerSideRendering|^Deno\//.test(window.navigator.userAgent) // useEffect can be executed on the server (NodeJs), but uselayouteeffect cannot const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect export default function create(createState) { const api = typeof createState === "function" ? createImpl(createState) : createState // Returns the useStore function for external use // Closures enable the api to be used internally by useStore as an execution context to ensure data isolation const useStore = (selector, equalityFn = Object.is) => { // Used to trigger component updates const [, forceUpdate] = useReducer((c) => c + 1, 0) // Get state const state = api.getState() // Mount the state to useRef to avoid side effects and update const stateRef = useRef(state) // Mount the specified selector method to useRef // Columns such as const bears = usestore (state = > state. Bears) const selectorRef = useRef(selector) // Equivalent method const equalityFnRef = useRef(equalityFn) // Marking error const erroredRef = useRef(false) // Current state attribute (state.bears) const currentSliceRef = useRef() // Null value processing if (currentSliceRef.current === undefined) { currentSliceRef.current = selector(state) } let newStateSlice let hasNewStateSlice = false // The selector or equalityFn need to be called during the render phase if // they change. We also want legitimate errors to be visible so we re-run // them if they errored in the subscriber. if ( stateRef.current !== state || selectorRef.current !== selector || equalityFnRef.current !== equalityFn || erroredRef.current ) { // Using local variables to avoid mutations in the render phase. newStateSlice = selector(state) // Are the old and new values equal hasNewStateSlice = !equalityFn(currentSliceRef.current, newStateSlice) } // Syncing changes in useEffect. useIsomorphicLayoutEffect(() => { if (hasNewStateSlice) { currentSliceRef.current = newStateSlice } stateRef.current = state selectorRef.current = selector equalityFnRef.current = equalityFn erroredRef.current = false }) // Staging state const stateBeforeSubscriptionRef = useRef(state) // initialization useIsomorphicLayoutEffect(() => { const listener = () => { try { // Latest get state when the update is triggered const nextState = api.getState() // Inject nextState, execute the passed in selector method, and get the value, that is, state.bears const nextStateSlice = selectorRef.current(nextState) // Comparison inequality = = > Update if (!equalityFnRef.current(currentSliceRef.current, nextStateSlice)) { // Update stateRef to the latest state stateRef.current = nextState // Update currentSliceRef to the latest property value, i.e. state.bears currentSliceRef.current = nextStateSlice // Update component forceUpdate() } } catch (error) { // Registration error erroredRef.current = true // Update component forceUpdate() } } // Add listener subscription const unsubscribe = api.subscribe(listener) // The state has been changed. Please notify me of the update if (api.getState() !== stateBeforeSubscriptionRef.current) { listener() // state has changed before subscription } // Clear subscription on uninstall return unsubscribe }, []) return hasNewStateSlice ? newStateSlice : currentSliceRef.current } // Merge api properties to useStore Object.assign(useStore, api) // Closures expose the only way for external use return useStore }
- In a brief summary
-
Create a store, get the unique interface useStore, and define the global state.
-
Obtain the status through const bears = usestore (state = > state. Bears) and bind it to the component.
- In this step, the store will perform the subscribe(listener) add subscription operation, and the method has a built-in forceUpdate() function to trigger the component update.
-
Use the set hook function to modify the state.
- That is, the called setState method, which will execute listeners. Foreach ((listener) = > listener (state, previousstate)) to notify all subscribers to update.
epilogue
Observer mode and publish subscribe mode are very common in actual projects. Many excellent third-party libraries also learn from the ideas of these two design modes - such as Vue, Vue Event, React Event, RxJS, Redux, zustand, etc.
It is very helpful for decoupling some logic or solving some asynchronous problems in the project. It is no exaggeration to say that publish subscribe mode / observer mode can solve most decoupling problems.
Generally speaking, reading and learning some excellent libraries (including some tool functions encapsulated in them, with many ingenious designs and implementations) is very helpful for our own growth and technological expansion. Many times, we will be convinced by the unique ideas and designs of the big guys, and through further understanding and mastery, we can fully absorb them for our own use, It's not beautiful to show your skills in practical projects in the future! 😎
Use case
https://github.com/JS-banana/subob-subpub
reference resources
- Observer mode vs publish subscribe mode
- Introduce the differences between the observer mode and the subscription publish mode, and what scenarios they are applicable to
- The observer mode is really different from the publish subscribe mode
- Publish subscribe mode
- Deep publish subscribe mode
- Observer mode of JavaScript Design Pattern
- Observer model of JS design pattern
- Observer mode (JavaScript Implementation)
- vue
- zustand
- Difference between uselayouteeffect and useEffect