A preface
Hello, I’m 👽. We’re going to publish a new series on React V18, which will focus on the background, features, and principles of the new features.
UseMutableSource’s original RFC proposal began in February 2020. It will be a new feature in React 18. Summarize useMutableSource with a description from the proposal.
UseMutableSource enables React components to safely and efficiently read external data sources in Concurrent Mode, detect changes during component rendering, and schedule updates when data sources change.
When it comes to external data sources, we need to start with state and update. Both React and Vue traditional UI frameworks adopt the virtual DOM approach, but they still cannot delegate update units to the virtual DOM. Therefore, the minimum granularity of update is still at the component level. The component manages data state uniformly and participates in scheduling updates.
Back to our React hero, since the component controls state. In V17 and previous versions, React wanted updates on views only by changing the internal data state. The React update mode is not dependent on its own state. Let’s take a look at the React update modes.
- The component itself changes state. function
useState
|useReducer
That kind of componentsetState
|forceUpdate
。 props
Change, the update of a child component brought about by a component update.context
Update, and the component consumes the currentcontext
。
Either way, it’s essentially a change of state.
props
Changes from the parent componentstate
Change.context
The change comes from the change of value in the Provider, and value is generally state or a derivative of state.
Model => View But state is limited to data inside the component, if state comes from outside (outside the component level). How do you convert an external data source to an internal state, change the data source, and re-render the component?
Normal mode, the external Data first external Data through the selector selector of Data mapping component needs to state | props. This completes the process, and then you subscribe to changes in the external data source and force forceUpdate itself if any changes occur. The following two graphs represent data injection and data subscription updates.
The typical external data source is the store in Redux, and how Redux safely converts the state in the store to the state in the component.
Perhaps I could use a piece of code to represent the flow from state changes to view updates in React-Redux.
const store = createStore(reducer,initState)
function App({ selector }){
const [ state , setReduxState ] = React.useState({})
const contextValue = useMemo(() = >{
/* Subscription store changes */
store.subscribe(() = >{
/* Select the subscription state */ with the selector
const value = selector(data.getState())
/* If there are changes */
if(ifHasChange(state,value)){
setReduxState(value)
}
})
},[ store ])
return <div>.</div>
}
Copy the code
But the code in this example, it’s not really meaningful, it’s not source code, so I’m just going to give you a clear idea of the flow. Redux and React essentially work like this.
- Subscribe to state changes with store.subscribe, but it’s essentially more complicated than in the snippet, finding the required state for the component using a selector. Let me explain it hereselectorIn this case, you need to select ‘useful’ from state and merge it with props. The selectors need to be supported by and
react-redux
Connect the first parameter mapStateToProps For the details, it doesn’t matter, because the point today isuseMutableSource
.
In the absence of useMutableSource, the subscription to update process is no longer handed over to the component. As follows:
/* Create store */
const store = createStore(reducer,initState)
/* Create external data source */
const externalDataSource = createMutableSource( store ,store.getState() )
/* Subscribe to update */
const subscribe = (store, callback) = > store.subscribe(callback);
function App({ selector }){
/* If the state of the subscription changes, the component is updated */
const state = useMutableSource(externalDataSource,selector,subscribe)
}
Copy the code
- Create external data sources through createMutableSource and use external data sources through useMutableSource. The external data source changes and the component is automatically rendered.
As shown above, subscription updates are implemented via useMutableSource, which reduces in-app component code, improves code robustness, and reduces coupling to some extent. Let’s take a look at all the new V18 features.
2 Function Introduction
For details, refer to the latest RFC, createMutableSource and useMutableSource. To some extent, they are similar to createContext and useContext. The difference is that the context requires the Provider to inject internal state, whereas today’s focus is on injecting external state. So we should first look at how both are used.
create
CreateMutableSource creates a data source. It takes two arguments:
const externalDataSource = createMutableSource( store ,store.getState() )
Copy the code
- The first parameter is an external data source, such as a store in Redux,
- The second parameter: a function, the return value of the function as the version number of the data source, note here ⚠️, to maintain the consistency of the data source and the data version number, that is, the data source changes, then the data version number must change, to some extent follow
immutable
Principle (immutability). It can be understood that the data version number is an indication to prove the uniqueness of the data source.
The API is introduced
UseMutableSource can use non-traditional data sources. It functions similar to Context API and useSubscription. (For students who have not used useSubscription, it is helpful to learn about it.)
First, take a look at the basic uses of useMutableSource:
const value = useMutableSource(source,getSnapShot,subscribe)
Copy the code
UseMutableSource is an hooks argument that takes three parameters:
Source: MutableSource < source >
You can think of it as a data source object with memory.GetSnapshot (source: source) => Snapshot
: a function that takes the data source as an argument to get snapshot informationselector
Filter the data of external data sources to find the desired data source.subscribe: (source: Source, callback: () => void) => () => void
Source is the first parameter of useMutableSource, and callback is the second parameter of useMutableSource. When the data Source changes, a snapshot is taken to retrieve new data.
UseMutableSource characteristics
UseMutableSource and useSubscription function are similar:
- Both require memorized ‘configured objects’ that can be evaluated externally.
- Both require a way to subscribe to and unsubscribe from sources
subscribe
.
Other features of useMutableSource include:
- UseMutableSource requires the source as an explicit parameter. That is, you need to pass in the data source object as the first parameter.
- UseMutableSource Data read with getSnapshot is immutable.
UseMutableSource tracks the version number of a MutableSource and then reads data, so if the two are inconsistent, a read exception may occur. UseMutableSource checks the version number:
- The version number is read the first time the component is mounted.
- When rerender is used, make sure the version number is the same and then read the data. Otherwise errors will occur.
- Ensure consistency of data source and version number.
The design specification
When reading an external data source via getSnapshot, the value returned should be immutable.
- ✅ : getSnapshot: source => array. from(source.friendids)
- ❌ error: getSnapshot: source => source.friendIDs
The data source must have a global version number that represents the entire data source:
- ✅ : getVersion: () => source.version
- ❌ error: getVersion: () => source.user.version
Next, I’ll refer to the github example to explain how to use it:
Example a
Example 1: Route changes in subscription history mode
For example, there is a scenario where the subscription route changes in a non-human situation. Show the corresponding location.pathname and see how it is handled using useMutableSource. In this scenario, the external data source is location information.
// Create an external data source through createMutableSource.
// The data source object is window.
// Use location.href as the version number of the data source.
const locationSource = createMutableSource(
window.() = > window.location.href
);
// Get snapshot information, this is the location. Pathname field, this can be reused, when the route changes, then the snapshot function will be called, to form a new snapshot information.
const getSnapshot = window= > window.location.pathname
// Subscribe function.
const subscribe = (window, callback) = > {
// Use popState to listen for route changes in history mode. When the route changes, run the snapshot function to obtain new snapshot information.
window.addEventListener("popstate", callback);
// Cancel the listener
return () = > window.removeEventListener("popstate", callback);
};
function Example() {
// Pass the data source object, snapshot function, and subscription function through useMutableSource to form a pathName.
const pathName = useMutableSource(locationSource, getSnapshot, subscribe);
// ...
}
Copy the code
To describe the process:
- First of all by
createMutableSource
Create a data source object called Window. Use location.href as the version number of the data source. If href changes, the data source has changed. - Obtain snapshot information, in this case the location. Pathname field, this can be reused, when the route changes, then the snapshot function will be called, to form a new snapshot information.
- through
popstate
Listening to thehistory
When routes change in mode, run the snapshot function to obtain new snapshot information. - through
useMutableSource
, pass in the data source object, snapshot function, subscription function, formpathName
。
While this example 🌰 may not be enough to get you started on useMutableSource, let’s take another example to see how useMutableSource works with Redux.
Example 2
Example 2: in reduxuseMutableSource
use
Redux can write custom hooks from useMutableSource — useSelector, which reads the state of the data source and re-captures the state when the data source changes, subscribes to updates. Let’s see how useSelector is implemented.
const mutableSource = createMutableSource(
reduxStore, // Use Redux's store as the data source.
// State is immutable and can be used as the version number of the data source
() = > reduxStore.getState()
);
// Save the mutableSource by creating a context.
const MutableSourceContext = createContext(mutableSource);
// Subscription store changes. If store changes, run getSnapshot
const subscribe = (store, callback) = > store.subscribe(callback);
// Custom hooks useSelector can be used inside each connect to get data source objects via useContext.
function useSelector(selector) {
const mutableSource = useContext(MutableSourceContext);
// use useCallback to make getSnapshot memory.
const getSnapshot = useCallback(store= > selector(store.getState()), [
selector
]);
// Finally, we essentially subscribe to state changes using useMutableSource.
return useMutableSource(mutableSource, getSnapshot, subscribe);
}
Copy the code
The general process is like this:
- Use Redux’s Store as the data source object
mutableSource
. State is immutable and can be used as the version number of the data source. - Save the data source object by creating a context
mutableSource
. - Declare subscription functions and subscribe store changes. Store changes, execute
getSnapshot
。 - Custom hooks
useSelector
It can be used within each connect, fetching data source objects via useContext. withuseCallback
Make getSnapshot memory. - The final result is essentially subscribing to external state changes with useMutableSource.
Pay attention to the problem
- GetSnapshot needs to be memorized when it is created, just as useCallback did with getSnapshot in the above process. If it is not memorized, components will be rendered frequently.
- In the latest react-Redux source code, the new API has been used to subscribe to external data sources, but not
useMutableSource
butuseSyncExternalStore
, specifically becauseuseMutableSource
There is no built-in selectorAPI, and you need to re-subscribe to the Store every time the selector changes, or if there is no memorization of the useCallback API, then re-subscribe. Please refer to the detailsUseMutableSource – useSyncExternalStore.
Three practice
I’m going to use an example to put the creation Mutablesource into practice to make the process clearer.
Again, redux and createMutableSource are used to reference external data sources. The 18.0.0-alpha versions of React and react- DOM are used here.
import React , {
unstable_useMutableSource as useMutableSource,
unstable_createMutableSource as createMutableSource
} from 'react'
import { combineReducers , createStore } from 'redux'
/* number Reducer */
function numberReducer(state=1,action){
switch (action.type){
case 'ADD':
return state + 1
case 'DEL':
return state - 1
default:
return state
}
}
/* Register reducer */
const rootReducer = combineReducers({ number:numberReducer })
/* Synthesizes Store */
const Store = createStore(rootReducer,{ number: 1 })
/* Register external data source */
const dataSource = createMutableSource( Store ,() = > 1 )
/* Subscribe to external data sources */
const subscribe = (dataSource,callback) = >{
const unSubScribe = dataSource.subscribe(callback)
return () = > unSubScribe()
}
/ *TODO:A * /
export default function Index(){
/* Get data snapshot */
const shotSnop = React.useCallback((data) = >({... data.getState()}),[])/* hooks: use */
const data = useMutableSource(dataSource,shotSnop,subscribe)
return <div>
<p>Embrace React 18 🎉🎉🎉</p>Praise: {data. The number}<br/>
<button onClick={()= >Store. Dispatch ({type:'ADD'})} > Like</button>
</div>
}
Copy the code
The first part uses combineReducers and createStore to create redux Store. The focus is on the second part:
- First create the data source with createMutableSource, Store as the data source,
data.getState()
As the version number. - The second point is snapshot information, which in this case is state in store. So in the
shotSnop
Again, get the status via getState, normally shotSnop shouldSelector
I’m mapping all the states here. - The third is to pass
useMutableSource
Pass in the data source, snapshot, and subscription functions, and the resulting data is the external data source referenced.
Let’s take a look at the results:
Four principles analysis
UseMutableSource is already planned for React V18, so the implementation principles and details will have to be adjusted in advance of the release of React V18.
1 createMutableSource
react/src/ReactMutableSource.js -> createMutableSource
function createMutableSource(source,getVersion){
const mutableSource = {
_getVersion: getVersion,
_source: source,
_workInProgressVersionPrimary: null._workInProgressVersionSecondary: null};return mutableSource
}
Copy the code
The createMutableSource principle is very simple, similar to createContext and createRef. Create a createMutableSource object,
2 useMutableSource
The useMutableSource principle is also less mysterious, as it is up to the developer to inject the external data source into state and then write the subscription function. The idea behind useMutableSource is to take what developers need to do and make it their own 😂😂😂 so developers don’t have to write code for it. Essentially useState + useEffect:
- UseState is responsible for the update.
- UseEffect takes care of subscriptions.
And then let’s see how it works.
react-reconciler/src/ReactFiberHooks.new.js -> useMutableSource
function useMutableSource(hook,source,getSnapshot){
/* Get the version number */
const getVersion = source._getVersion;
const version = getVersion(source._source);
/* Save the current Snapshot with useState to trigger the update. * /
let [currentSnapshot, setSnapshot] = dispatcher.useState(() = >
readFromUnsubscribedMutableSource(root, source, getSnapshot),
);
dispatcher.useEffect(() = > {
/* Wrapping function */
const handleChange = () = > {
/* Trigger update */
setSnapshot()
}
/* Subscribe to update */
const unsubscribe = subscribe(source._source, handleChange);
/* Unsubscribe */
return unsubscribe;
},[source, subscribe])
}
Copy the code
The core logic is preserved in the above code:
- First of all by
getVersion
To get the data source version number, useuseState
Save the current Snapshot. SetSnapshot is used to trigger the update. - In useEffect, the subscription is bound to the wrapped handleChange function, which calls the setSnapshot real update component.
- So useMutableSource is essentially useState.
Five summarizes
Today covers the background, usage, and rationale of useMutableSource. If you want to read React V18, you can clone the new version and try out the new features that will help you understand useMutableSource. The next chapter will continue to focus on React V18.
This article is also synchronized to the booklet, interested students can also read in the booklet system.
Reference documentation
- useMutableSource RFC