40 line program integrates the response of Vue3 into React for state management

Keywords: Programming Vue React TypeScript

This article refers to the original- http://bjbsair.com/2020-03-22/tech-info/2095/

Preface

Vue next is the source warehouse of vue3. Vue3 uses lerna to divide the package, while the responsive capability @ vue/reactivity is divided into a separate package.

If we want to integrate it into React, is it feasible? Let's have a try.

Use example

If you don't talk much, let's see how to use it to satisfy your craving.

// store.ts  
import { reactive, computed, effect } from '@vue/reactivity';  
  
export const state = reactive({  
  count: 0,  
});  
  
const plusOne = computed(() => state.count + 1);  
  
effect(() => {  
  console.log('plusOne changed: ', plusOne);  
});  
  
const add = () => (state.count += 1);  
  
export const mutations = {  
  // mutation  
  add,  
};  
  
export const store = {  
  state,  
  computed: {  
    plusOne,  
  },  
};  
  
export type Store = typeof store;  
//Copy code
// Index.tsx  
import { Provider, useStore } from 'rxv'  
import { mutations, store, Store } from './store.ts'  
function Count() {  
  const countState = useStore((store: Store) => {  
    const { state, computed } = store;  
    const { count } = state;  
    const { plusOne } = computed;  
  
    return {  
      count,  
      plusOne,  
    };  
  });  
  
  return (  
    <Card hoverable style={{ marginBottom: 24 }}>  
      <h1>Counter</h1>  
      <div class>  
        <div class>store Medium count Now is {countState.count}</div>  
        <div class>computed In value plusOne Now is {countState.plusOne.value}</div>  
         <Button onClick={mutations.add}>add</Button>  
      </div>  
    </Card>  
  );  
}  
  
export default () => {  
  return (  
    <Provider value={store}>  
       <Count />  
    </Provider>  
  );  
};  
//Copy code

It can be seen that the definition of store only uses @ Vue / activity, while rxv only makes a layer of bridge in the component, connecting Vue3 and React, and then we can use Vue3's response ability to our fullest.

preview

It can be seen that the powerful capabilities of reactive and computed are perfectly utilized.

Analysis

Analyze from several core APIs provided by this package:

effect (key)

effect is actually a general concept in response Library: observation function, like Watcher in Vue2, autorun and observer in mobx, is used to collect dependencies.

It accepts a function, it will help you execute this function, and enable dependency collection,

The function can collect and depend on the access to responsive data, so the update will be triggered after the responsive data is modified.

Simplest usage

const data = reactive({ count: 0 })  
effect(() => {  
    // This is the sentence that accesses data.count  
    // To collect the dependency  
    console.log(data.count)  
})  
  
data.count = 1  
// Console print out 1  
//Copy code

So if you take the

() => {  
    //This is the sentence that accesses data.count  
    //To collect the dependency  
    console.log(data.count)  
}  
Copy code

Is this function, instead of React component rendering, able to achieve the purpose of updating components in response?

reactive (key)

The core api of responsive data. This api returns a proxy. Access to all the above properties will be hijacked, so as to collect dependencies (i.e. running effect s) during get and trigger updates during set.

ref

For simple data types such as number, we can't do it like this:

let data = reactive(2)  
// oops  
data = 5  
Copy code

This is not in line with the responsive interception rules. There is no way to intercept the change of data itself, only the property change of data, so there is ref.

const data = ref(2)  
// ok  
data.value= 5  
Copy code

computed

When a dependency value is updated, its value will be automatically updated. In fact, computed is also an effect.

It has advanced features such as observing another computed data in computed, and observing computed changes with effect.

Realization

From these core APIs, as long as the effect can be connected to the React system, then other APIs have no problem, because they only collect the dependency of the effect and notify the effect to trigger the update.

effect accepts a function, and it also supports what function to trigger when defining dependency updates by passing in the schedule parameter. What if we replace this schedule with the update of the corresponding component? You should know that in the world of hook, it is very simple to implement the forced update of current components:

useForceUpdate

export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

This is a classic custom hook, which forces the component to render by constantly adding + 1 state.

The core api: useStore of rxv also accepts a function selector, which allows users to choose the data they need to access in the component.

So the idea is obvious:

  1. The selector is wrapped in an effect to collect dependencies.
  2. Specifies that when the dependency is updated, the function to be called is the forceUpdate forced rendering function of this component that is currently using useStore.

In this way, data changes are realized and components are updated automatically?

Simply look at the core implementation

useStore and Provider

import React, { useContext } from 'react';  
import { useForceUpdate, useEffection } from './share';  
  
type Selector<T, S> = (store: T) => S;  
  
const StoreContext = React.createContext<any>(null);  
  
const useStoreContext = () => {  
  const contextValue = useContext(StoreContext);  
  if (!contextValue) {  
    throw new Error(  
      'could not find store context value; please ensure the component is wrapped in a <Provider>',  
    );  
  }  
  return contextValue;  
};  
  
/**  
 * Read global state in component  
 * Dependencies need to be collected through the passed in function  
 */  
export const useStore = <T, S>(selector: Selector<T, S>): S => {  
  const forceUpdate = useForceUpdate();  
  const store = useStoreContext();  
  
  const effection = useEffection(() => selector(store), {  
    scheduler: forceUpdate,  
    lazy: true,  
  });  
  
  const value = effection();  
  return value;  
};  
  
export const Provider = StoreContext.Provider;  
  
//Copy code

This option is passed to the effect API of Vue3,

The scheduler specifies what to do after the reactive data update. Here we use forceUpdate to re render the component.

lazy means delayed execution. Later, we call the effect manually to execute

  
{  
  scheduler: forceUpdate,  
  lazy: true,  
}  
Copy code

Let's take a look at useeffect and useForceUpdate

import { useEffect, useReducer, useRef } from 'react';  
import { effect, stop, ReactiveEffect } from '@vue/reactivity';  
  
export const useEffection = (...effectArgs: Parameters<typeof effect>) => {  
  // Use a ref to store the effect  
  // The effect function only needs to be initialized and executed once  
  const effectionRef = useRef<ReactiveEffect>();  
  if (!effectionRef.current) {  
    effectionRef.current = effect(...effectArgs);  
  }  
  
  // Cancel effect after uninstalling components  
  const stopEffect = () => {  
    stop(effectionRef.current!);  
  };  
  useEffect(() => stopEffect, []);  
  
  return effectionRef.current  
};  
  
export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

It's also very simple, that is, to give the passed in function to effect and stop the effect when the component is destroyed.

Technological process

  1. First register a forced update function in the current component through useForceUpdate.
  2. Read the store that the user passed in from the Provider through useContext.
  3. Then, Vue's effect is used to execute the selector(store) for us, and the scheduler is specified as forceUpdate, so the dependency collection is completed.
  4. After the value in the store is updated, the scheduler, i.e. forceUpdate, is triggered, and our React component is automatically updated.

In a few simple lines of code, all the capabilities in @ vue/reactivity are implemented in React.

Advantage:

  1. Directly introduce @ Vue / reacity, fully use the reactivity capability of Vue3, have calculated, effect and other capabilities, and also provide responsive capabilities for Set and Map. In the future, it will become more perfect and powerful with the update of this library.
  2. Complete test cases in Vue next warehouse.
  3. Full TypeScript type support.
  4. Fully reuse @ Vue / reacity to achieve strong global state management capabilities.
  5. Precise update of component level in state management.
  6. Vue3 always has to learn, learn ahead of time to prevent unemployment!

Disadvantages:

  1. Because the need for accurate collection depends on useStore, the selector function must accurately access the data you care about. Even if you need to trigger an update of a value within an array, you can't just return the array itself in useStore.

For example:

function Logger() {  
  const logs = useStore((store: Store) => {  
    return store.state.logs.map((log, idx) => (  
      <p class key={idx}>  
        {log}  
      </p>  
    ));  
  });  
  
  return (  
    <Card hoverable>  
      <h1>Console</h1>  
      <div class>{logs}</div>  
    </Card>  
  );  
}  
//Copy code

This code directly returns the whole jsx in useStore, because it accesses every item of the array to collect dependencies during the process of map, so as to achieve responsive purpose. This article refers to the original- http://bjbsair.com/2020-03-22/tech-info/2095/

Preface

Vue next is the source warehouse of vue3. Vue3 uses lerna to divide the package, while the responsive capability @ vue/reactivity is divided into a separate package.

If we want to integrate it into React, is it feasible? Let's have a try.

Use example

If you don't talk much, let's see how to use it to satisfy your craving.

// store.ts  
import { reactive, computed, effect } from '@vue/reactivity';  
  
export const state = reactive({  
  count: 0,  
});  
  
const plusOne = computed(() => state.count + 1);  
  
effect(() => {  
  console.log('plusOne changed: ', plusOne);  
});  
  
const add = () => (state.count += 1);  
  
export const mutations = {  
  // mutation  
  add,  
};  
  
export const store = {  
  state,  
  computed: {  
    plusOne,  
  },  
};  
  
export type Store = typeof store;  
//Copy code
// Index.tsx  
import { Provider, useStore } from 'rxv'  
import { mutations, store, Store } from './store.ts'  
function Count() {  
  const countState = useStore((store: Store) => {  
    const { state, computed } = store;  
    const { count } = state;  
    const { plusOne } = computed;  
  
    return {  
      count,  
      plusOne,  
    };  
  });  
  
  return (  
    <Card hoverable style={{ marginBottom: 24 }}>  
      <h1>Counter</h1>  
      <div class>  
        <div class>store Medium count Now is {countState.count}</div>  
        <div class>computed In value plusOne Now is {countState.plusOne.value}</div>  
         <Button onClick={mutations.add}>add</Button>  
      </div>  
    </Card>  
  );  
}  
  
export default () => {  
  return (  
    <Provider value={store}>  
       <Count />  
    </Provider>  
  );  
};  
//Copy code

It can be seen that the definition of store only uses @ Vue / activity, while rxv only makes a layer of bridge in the component, connecting Vue3 and React, and then we can use Vue3's response ability to our fullest.

preview

It can be seen that the powerful capabilities of reactive and computed are perfectly utilized.

Analysis

Analyze from several core APIs provided by this package:

effect (key)

effect is actually a general concept in response Library: observation function, like Watcher in Vue2, autorun and observer in mobx, is used to collect dependencies.

It accepts a function, it will help you execute this function, and enable dependency collection,

The function can collect and depend on the access to responsive data, so the update will be triggered after the responsive data is modified.

Simplest usage

const data = reactive({ count: 0 })  
effect(() => {  
    // This is the sentence that accesses data.count  
    // To collect the dependency  
    console.log(data.count)  
})  
  
data.count = 1  
// Console print out 1  
//Copy code

So if you take the

() => {  
    //This is the sentence that accesses data.count  
    //To collect the dependency  
    console.log(data.count)  
}  
Copy code

Is this function, instead of React component rendering, able to achieve the purpose of updating components in response?

reactive (key)

The core api of responsive data. This api returns a proxy. Access to all the above properties will be hijacked, so as to collect dependencies (i.e. running effect s) during get and trigger updates during set.

ref

For simple data types such as number, we can't do it like this:

let data = reactive(2)  
// oops  
data = 5  
Copy code

This is not in line with the responsive interception rules. There is no way to intercept the change of data itself, only the property change of data, so ref is available.

const data = ref(2)  
// ok  
data.value= 5  
Copy code

computed

When a dependency value is updated, its value will be automatically updated. In fact, computed is also an effect.

It has advanced features such as observing another computed data in computed, and observing computed changes with effect.

Realization

From these core APIs, as long as the effect can be connected to the React system, then other APIs have no problem, because they only collect the dependency of the effect and notify the effect to trigger the update.

effect accepts a function, and it also supports what function to trigger when defining dependency updates by passing in the schedule parameter. What if we replace this schedule with the update of the corresponding component? You should know that in the world of hook, it is very simple to implement the forced update of current components:

useForceUpdate

export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

This is a classic custom hook, which forces the component to render by constantly adding + 1 state.

The core api: useStore of rxv also accepts a function selector, which allows users to choose the data they need to access in the component.

So the idea is obvious:

  1. The selector is wrapped in an effect to collect dependencies.
  2. Specifies that when the dependency is updated, the function to be called is the forceUpdate forced rendering function of this component that is currently using useStore.

In this way, data changes are realized and components are updated automatically?

Simply look at the core implementation

useStore and Provider

import React, { useContext } from 'react';  
import { useForceUpdate, useEffection } from './share';  
  
type Selector<T, S> = (store: T) => S;  
  
const StoreContext = React.createContext<any>(null);  
  
const useStoreContext = () => {  
  const contextValue = useContext(StoreContext);  
  if (!contextValue) {  
    throw new Error(  
      'could not find store context value; please ensure the component is wrapped in a <Provider>',  
    );  
  }  
  return contextValue;  
};  
  
/**  
 * Read global state in component  
 * Dependencies need to be collected through the passed in function  
 */  
export const useStore = <T, S>(selector: Selector<T, S>): S => {  
  const forceUpdate = useForceUpdate();  
  const store = useStoreContext();  
  
  const effection = useEffection(() => selector(store), {  
    scheduler: forceUpdate,  
    lazy: true,  
  });  
  
  const value = effection();  
  return value;  
};  
  
export const Provider = StoreContext.Provider;  
  
//Copy code

This option is passed to the effect API of Vue3,

The scheduler specifies what to do after the reactive data update. Here we use forceUpdate to re render the component.

lazy means delayed execution. Later, we call the effect manually to execute

  
{  
  scheduler: forceUpdate,  
  lazy: true,  
}  
Copy code

Let's take a look at useeffect and useForceUpdate

import { useEffect, useReducer, useRef } from 'react';  
import { effect, stop, ReactiveEffect } from '@vue/reactivity';  
  
export const useEffection = (...effectArgs: Parameters<typeof effect>) => {  
  // Use a ref to store the effect  
  // The effect function only needs to be initialized and executed once  
  const effectionRef = useRef<ReactiveEffect>();  
  if (!effectionRef.current) {  
    effectionRef.current = effect(...effectArgs);  
  }  
  
  // Cancel effect after uninstalling components  
  const stopEffect = () => {  
    stop(effectionRef.current!);  
  };  
  useEffect(() => stopEffect, []);  
  
  return effectionRef.current  
};  
  
export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

It's also very simple, that is, to give the passed in function to effect and stop the effect when the component is destroyed.

Technological process

  1. First register a forced update function in the current component through useForceUpdate.
  2. Read the store that the user passed in from the Provider through useContext.
  3. Then, Vue's effect is used to execute the selector(store) for us, and the scheduler is specified as forceUpdate, so the dependency collection is completed.
  4. After the value in the store is updated, the scheduler, i.e. forceUpdate, is triggered, and our React component is automatically updated.

In a few simple lines of code, all the capabilities in @ vue/reactivity are implemented in React.

Advantage:

  1. Directly introduce @ Vue / reacity, fully use the reactivity capability of Vue3, have calculated, effect and other capabilities, and also provide responsive capabilities for Set and Map. In the future, it will become more perfect and powerful with the update of this library.
  2. Complete test cases in Vue next warehouse.
  3. Full TypeScript type support.
  4. Fully reuse @ Vue / reacity to achieve strong global state management capabilities.
  5. Precise update of component level in state management.
  6. Vue3 always has to learn, learn ahead of time to prevent unemployment!

Disadvantages:

  1. Because the need for accurate collection depends on useStore, the selector function must accurately access the data you care about. Even if you need to trigger an update of a value within an array, you can't just return the array itself in useStore.

For example:

function Logger() {  
  const logs = useStore((store: Store) => {  
    return store.state.logs.map((log, idx) => (  
      <p class key={idx}>  
        {log}  
      </p>  
    ));  
  });  
  
  return (  
    <Card hoverable>  
      <h1>Console</h1>  
      <div class>{logs}</div>  
    </Card>  
  );  
}  
//Copy code

This code directly returns the whole jsx in useStore, because it accesses every item of the array to collect dependencies during the process of map, so as to achieve responsive purpose. This article refers to the original- http://bjbsair.com/2020-03-22/tech-info/2095/

Preface

Vue next is the source warehouse of vue3. Vue3 uses lerna to divide the package, while the responsive capability @ vue/reactivity is divided into a separate package.

If we want to integrate it into React, is it feasible? Let's have a try.

Use example

If you don't talk much, let's see how to use it to satisfy your craving.

// store.ts  
import { reactive, computed, effect } from '@vue/reactivity';  
  
export const state = reactive({  
  count: 0,  
});  
  
const plusOne = computed(() => state.count + 1);  
  
effect(() => {  
  console.log('plusOne changed: ', plusOne);  
});  
  
const add = () => (state.count += 1);  
  
export const mutations = {  
  // mutation  
  add,  
};  
  
export const store = {  
  state,  
  computed: {  
    plusOne,  
  },  
};  
  
export type Store = typeof store;  
//Copy code
// Index.tsx  
import { Provider, useStore } from 'rxv'  
import { mutations, store, Store } from './store.ts'  
function Count() {  
  const countState = useStore((store: Store) => {  
    const { state, computed } = store;  
    const { count } = state;  
    const { plusOne } = computed;  
  
    return {  
      count,  
      plusOne,  
    };  
  });  
  
  return (  
    <Card hoverable style={{ marginBottom: 24 }}>  
      <h1>Counter</h1>  
      <div class>  
        <div class>store Medium count Now is {countState.count}</div>  
        <div class>computed In value plusOne Now is {countState.plusOne.value}</div>  
         <Button onClick={mutations.add}>add</Button>  
      </div>  
    </Card>  
  );  
}  
  
export default () => {  
  return (  
    <Provider value={store}>  
       <Count />  
    </Provider>  
  );  
};  
//Copy code

It can be seen that the definition of store only uses @ Vue / activity, while rxv only makes a layer of bridge in the component, connecting Vue3 and React, and then we can use Vue3's response ability to our fullest.

preview

It can be seen that the powerful capabilities of reactive and computed are perfectly utilized.

Analysis

Analyze from several core APIs provided by this package:

effect (key)

effect is actually a general concept in response Library: observation function, like Watcher in Vue2, autorun and observer in mobx, is used to collect dependencies.

It accepts a function, it will help you execute this function, and enable dependency collection,

The function can collect and depend on the access to responsive data, so the update will be triggered after the responsive data is modified.

Simplest usage

const data = reactive({ count: 0 })  
effect(() => {  
    // This is the sentence that accesses data.count  
    // To collect the dependency  
    console.log(data.count)  
})  
  
data.count = 1  
// Console print out 1  
//Copy code

So if you take the

() => {  
    //This is the sentence that accesses data.count  
    //To collect the dependency  
    console.log(data.count)  
}  
Copy code

Is this function, instead of React component rendering, able to achieve the purpose of updating components in response?

reactive (key)

The core api of responsive data. This api returns a proxy. Access to all the above properties will be hijacked, so as to collect dependencies (i.e. running effect s) during get and trigger updates during set.

ref

For simple data types such as number, we can't do it like this:

let data = reactive(2)  
// oops  
data = 5  
Copy code

This is not in line with the responsive interception rules. There is no way to intercept the change of data itself, only the property change of data, so there is ref.

const data = ref(2)  
// ok  
data.value= 5  
Copy code

computed

When a dependency value is updated, its value will be automatically updated. In fact, computed is also an effect.

It has advanced features such as observing another computed data in computed, and observing computed changes with effect.

Realization

From these core APIs, as long as the effect can be connected to the React system, then other APIs have no problem, because they only collect the dependency of the effect and notify the effect to trigger the update.

effect accepts a function, and it also supports what function to trigger when defining dependency updates by passing in the schedule parameter. What if we replace this schedule with the update of the corresponding component? You should know that in the world of hook, it is very simple to implement the forced update of current components:

useForceUpdate

export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

This is a classic custom hook, which forces the component to render by constantly adding + 1 state.

The core api: useStore of rxv also accepts a function selector, which allows users to choose the data they need to access in the component.

So the idea is obvious:

  1. The selector is wrapped in an effect to collect dependencies.
  2. Specifies that when the dependency is updated, the function to be called is the forceUpdate forced rendering function of this component that is currently using useStore.

In this way, data changes are realized and components are updated automatically?

Simply look at the core implementation

useStore and Provider

import React, { useContext } from 'react';  
import { useForceUpdate, useEffection } from './share';  
  
type Selector<T, S> = (store: T) => S;  
  
const StoreContext = React.createContext<any>(null);  
  
const useStoreContext = () => {  
  const contextValue = useContext(StoreContext);  
  if (!contextValue) {  
    throw new Error(  
      'could not find store context value; please ensure the component is wrapped in a <Provider>',  
    );  
  }  
  return contextValue;  
};  
  
/**  
 * Read global state in component  
 * Dependencies need to be collected through the passed in function  
 */  
export const useStore = <T, S>(selector: Selector<T, S>): S => {  
  const forceUpdate = useForceUpdate();  
  const store = useStoreContext();  
  
  const effection = useEffection(() => selector(store), {  
    scheduler: forceUpdate,  
    lazy: true,  
  });  
  
  const value = effection();  
  return value;  
};  
  
export const Provider = StoreContext.Provider;  
  
//Copy code

This option is passed to the effect API of Vue3,

The scheduler specifies what to do after the reactive data update. Here we use forceUpdate to re render the component.

lazy means delayed execution. Later, we call the effect manually to execute

  
{  
  scheduler: forceUpdate,  
  lazy: true,  
}  
Copy code

Let's take a look at useeffect and useForceUpdate

import { useEffect, useReducer, useRef } from 'react';  
import { effect, stop, ReactiveEffect } from '@vue/reactivity';  
  
export const useEffection = (...effectArgs: Parameters<typeof effect>) => {  
  // Use a ref to store the effect  
  // The effect function only needs to be initialized and executed once  
  const effectionRef = useRef<ReactiveEffect>();  
  if (!effectionRef.current) {  
    effectionRef.current = effect(...effectArgs);  
  }  
  
  // Cancel effect after uninstalling components  
  const stopEffect = () => {  
    stop(effectionRef.current!);  
  };  
  useEffect(() => stopEffect, []);  
  
  return effectionRef.current  
};  
  
export const useForceUpdate = () => {  
  const [, forceUpdate] = useReducer(s => s + 1, 0);  
  return forceUpdate;  
};  
//Copy code

It's also very simple, that is, to give the passed in function to effect and stop the effect when the component is destroyed.

Technological process

  1. First register a forced update function in the current component through useForceUpdate.
  2. Read the store that the user passed in from the Provider through useContext.
  3. Then, Vue's effect is used to execute the selector(store) for us, and the scheduler is specified as forceUpdate, so the dependency collection is completed.
  4. After the value in the store is updated, the scheduler, i.e. forceUpdate, is triggered, and our React component is automatically updated.

In a few simple lines of code, all the capabilities in @ vue/reactivity are implemented in React.

Advantage:

  1. Directly introduce @ Vue / reacity, fully use the reactivity capability of Vue3, have calculated, effect and other capabilities, and also provide responsive capabilities for Set and Map. In the future, it will become more perfect and powerful with the update of this library.
  2. Complete test cases in Vue next warehouse.
  3. Full TypeScript type support.
  4. Fully reuse @ Vue / reacity to achieve strong global state management capabilities.
  5. Precise update of component level in state management.
  6. Vue3 always has to learn, learn ahead of time to prevent unemployment!

Disadvantages:

  1. Because the need for accurate collection depends on useStore, the selector function must accurately access the data you care about. Even if you need to trigger an update of a value within an array, you can't just return the array itself in useStore.

For example:

function Logger() {  
  const logs = useStore((store: Store) => {  
    return store.state.logs.map((log, idx) => (  
      <p class key={idx}>  
        {log}  
      </p>  
    ));  
  });  
  
  return (  
    <Card hoverable>  
      <h1>Console</h1>  
      <div class>{logs}</div>  
    </Card>  
  );  
}  
//Copy code

This code directly returns the whole jsx in useStore, because it accesses every item of the array to collect dependencies during the process of map, so as to achieve responsive purpose.

Posted by double on Tue, 24 Mar 2020 01:02:28 -0700