欲しいもの - reduxのソースコード解析
皆さんこんにちは、今日はredux(v3.6.0)のソースコード解析についてお届けします〜。
まず、reduxのgithubアドレスです。 クリックミー
次に、私たちのプロジェクトにおけるreduxの簡単な使い方について見てみましょう。
注:この例はreactで使用されていますが、もちろんreduxはreactだけでなく、市場にある他のほとんどのフレームワークで動作しますし、そこが少し欠点でもあります
最初のステップは、ストアを作成することです。
import React from 'react'
import { render } from 'react-dom'
// First we must import the createStore method in redux to create the store
// import the applyMiddleware method to use the middleware
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
// import redux's middleware thunk
import thunk from 'redux-thunk'
// import redux's middleware createLogger
import { createLogger } from 'redux-logger'
// We must also define our own reducer function to access the new state based on the action we passed in
import reducer from '. /reducers'
import App from '. /containers/App'
// Create an array of middleware
const middleware = [ thunk ]
if (process.env.NODE_ENV ! == 'production') {
middleware.push(createLogger())
}
// Call the createStore method to create the store, passing in the reducer and the function that applies the middleware, respectively
const store = createStore(
reducer,
applyMiddleware(... . middleware)
)
// Pass the store as an attribute so that you can get the store instance in each child component and use the store's methods
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
次に、reducer がどのように定義されているかを見てみましょう。
// First we import the combineReducers method in redux
import { combineReducers } from 'redux'
// Import actions, this is not required but recommended
import {
SELECT_REDDIT, INVALIDATE_REDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from '... /actions'
// The next two methods selectedReddit, postsByReddit are the reducer methods
// The reducer method is responsible for returning the new state according to the type of action passed in, here you can pass in the default state
const selectedReddit = (state = 'reactjs', action) => {
switch (action.type) {
case SELECT_REDDIT:
return action.reddit
default:
return state
}
}
const posts = (state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) => {
switch (action.type) {
case INVALIDATE_REDDIT:
return {
... . state,
didInvalidate: true
}
case REQUEST_POSTS:
return {
... . state,
isFetching: true,
didInvalidate: false
}
case RECEIVE_POSTS:
return {
... . state,
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
}
default:
return state
}
}
const postsByReddit = (state = { }, action) => {
switch (action.type) {
case INVALIDATE_REDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return {
... . state,
[action.reddit]: posts(state[action.reddit], action)
}
default:
return state
}
}
// Finally, we combine all the reducer methods into one method, the rootReducer method, via the combineReducers method
const rootReducer = combineReducers({
postsByReddit,
selectedReddit
})
// Export this rootReducer method
export default rootReducer
次のステップでは、アクションの定義を見ていきます。アクションは、必須属性の type と非必須属性の payload を持つオブジェクトです。type はアクションの種類を表し、状態を修正するアクションの意図を指定し、一方 payload はリデューサが使用する追加データを渡す方法です。
export const REQUEST_POSTS = 'REQUEST_POSTS'
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
export const SELECT_REDDIT = 'SELECT_REDDIT'
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'
export const selectReddit = reddit => ({
type: SELECT_REDDIT,
reddit
})
export const invalidateReddit = reddit => ({
type: INVALIDATE_REDDIT,
reddit
})
export const requestPosts = reddit => ({
type: REQUEST_POSTS,
reddit
})
export const receivePosts = (reddit, json) => ({
type: RECEIVE_POSTS,
reddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
})
const fetchPosts = reddit => dispatch => {
dispatch(requestPosts(reddit))
return fetch(`https://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)))
}
const shouldFetchPosts = (state, reddit) => {
const posts = state.postsByReddit[reddit]
if (!posts) {
return true
}
if (posts.isFetching) {
return false
}
return posts.didInvalidate
}
export const fetchPostsIfNeeded = reddit => (dispatch, getState) => {
if (shouldFetchPosts(getState(), reddit)) {
return dispatch(fetchPosts(reddit))
}
}
これはreduxの最もシンプルな使い方なので、reduxのソースコードでどのように実装されているのか見てみましょう。
まず、redux プロジェクト全体のディレクトリ構造を見てみましょう。ここから、redux プロジェクトのソースコードが実は比較的シンプルであることがわかります。
エントリーファイルのindex.jsから始めましょう。このファイルは実質的には何も実装していませんが、reduxが提供する機能をエクスポートしているだけです。
// The entry file
// First introduce the appropriate module, the details of which will be analyzed later
import createStore from '. /createStore'
import combineReducers from '. /combineReducers'
import bindActionCreators from '. /bindActionCreators'
import applyMiddleware from '. /applyMiddleware'
import compose from '. /compose'
import warning from '. /utils/warning'
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV ! == 'production', warn the user.
*/
function isCrushed() {}
if (
process.env.NODE_ENV ! == 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name ! == 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
' + 'This means that you are running a slower development build of Redux.
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin
' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
' to ensure you have the correct code for your production build.'
)
}
// Export the appropriate functions
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
その直後、reduxの重要なファイルであるcreateStore.jsを見てみましょう。
// The file that creates the store provides all the built-in functionality of the store in redux, and is one of the more important files in redux
// First introduce the appropriate module
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
// Defines an ActionType for internal use
export const ActionTypes = {
INIT: '@@redux/INIT'
}
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*There should only be a single store.
* There should only be a single store in your app.
* To specify how different parts of the state tree respond to actions, you may combine several reducers
To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`.
* @param
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a
You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys.
*
* @param {Function} [enhancer] The store enhancer.
You may optionally specify it * to enhance the store with third-party capabilities such as middleware,
You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux
The only store enhancer that ships with Redux * is `applyMiddleware()`.
The only store enhancer that ships with Redux * is `applyMiddleware()`.
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
// Export the method that creates the store
// This method takes three parameters, the reducer, the preloaded state, and the enhancement function enhancer
export default function createStore(reducer, preloadedState, enhancer) {
// adjust the parameters, if no preloaded state is passed and the second parameter is a function, then the second parameter is the enhancer function
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// determine that enhancer must be a function
if (typeof enhancer ! == 'undefined') {
if (typeof enhancer ! == 'function') {
throw new Error('Expec
function subscribe(listener) {
// The listener must be a function
if (typeof listener ! == 'function') {
throw new Error('Expected listener to be a function.')
}
// Declare a variable to mark whether it has been subscribed, which is cached via a closure
let isSubscribed = true
// Create a copy of the current currentListeners and assign it to nextListeners
ensureCanMutateNextListeners()
// push the listener function to nextListeners
nextListeners.push(listener)
// return a function to unlisten
// The principle is simple: remove the current function from the array, using the array's splice method
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/* * Dispatches an action.
* Dispatches an action. It is the only way to trigger a state change.
*It is the only way to trigger a state change.
It is the only way to trigger a state change. * The `reducer` function, used to create the store, will be called with the
The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will
Its return value will * be considered the * *next** state of the tree, and the change listeners
Its return value will * be considered the * *next** state of the tree, and the change listeners * will be notified.
*The base implementation only supports plain object.
* The base implementation only supports plain object actions.
* If you want to dispatch a Promise, an Observable, a thunk, or something else, you need to
If you want to dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware.
For * example, see the documentation for the `redux-thunk` package.
Even the * middleware will eventually dispatch plain object actions using this method.
Even the * middleware will eventually dispatch plain object actions using this method.
Even the * middleware will eventually dispatch plain object actions using this method. * @param {Object} action A plain object representing "what changed". It is
It is * a good idea to keep actions serializable so you can record and replay user
It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have
An action must have * a `type` property which may not be `undefined`. It is a good idea to use
It is a good idea to use * string constants for action types.
It is a good idea to use * string constants for action types.
It is a good idea to use * string constants for action types. * @returns {Object} For convenience, the same action object you dispatched.
It is a good idea to use * string constants for action types.
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
// An action is dispatched in redux to trigger a change to the state of the store
// The argument is an action
function dispatch(action) {
// Here we determine if the action is a pure object, and throw an error if it is not
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
' + 'Use custom middleware for async actions.'
)
}
// The action must have a type attribute, otherwise it throws an error
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
' + 'Have you misspelled a constant?'
)
}
// If the last dispatch has not finished, you cannot continue dispatching the next one
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// Set isDispatching to true to indicate the start of the current dispatch
isDispatching = true
// Use the incoming reducer function to process the state and action and return the new state
// It is recommended not to modify the currentState directly
currentState = currentReducer(currentState, action)
} finally {
// end of current dispatch
isDispatching = false
}
// After each dispatch, execute the listener function in the listener queue
// assign nextListeners to currentListeners to ensure that the next execution of ensureCanMutateNextListeners will recopy a new copy
// A simple and brutal for loop execution
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// Finally return the action
return action
}
/*
* Replaces the reducer currently used by the store to calculate the state.
*You might need this if your app implements code splitting and you want to calculate the state.
* You might need this if your app implements code splitting and you want to
* You might also need this if yo
次に combineReducers.js を見てみましょう。これは通常、reducer メソッドをマージするために使用します。
このファイルは、複数のリデューサをマージし、ルートリデューサを返すために使用されます。
ストアには1つのレデューサー機能しかないため、モジュール分割を行う場合はこのメソッドが必要です。
// Start by importing the appropriate functions
import { ActionTypes } from '. /createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from '. /utils/warning'
// Get the error message of UndefinedState
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'
return (
`Given action ${actionName}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state.
` If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
// Get all keys of reducers
const reducerKeys = Object.keys(reducers)
const argumentName = action && action.type === ActionTypes.INIT ?
'preloadedState argument passed to createStore' :
'previous state received by the reducer'
// When the reducers object is an empty object, return a warning text
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
// state must be an object
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
// determine if there are keys in the state that the reducer does not have, because when redux divides the state into modules, it is based on the reducer
const unexpectedKeys = Object.keys(inputState).filter(key =>
!reducers.hasOwnProperty(key) &&
!unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
` Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
// assertReducerShape function to check if the reducer will return an undefined when it encounters a position action, and throw an error if it does
// accept a reducers object
function assertReducerShape(reducers) {
// Iterate over the reducers object
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
// Get the value returned by the reducer function when the state is undefined and the actionType is the initial default type
const initialState = reducer(undefined, { type: ActionTypes.INIT })
// If the value is undefined, throw an error because the initial state should not be undefined
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
` + `explicitly return the initial state.
If you don't want to set a value for this reducer, ` + `you can use null instead of
` you can use null instead of undefined.`
)
}
// When an action is encountered that you don't know about, the reducer cannot return undefined either, otherwise it will also throw an error
const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
if (typeof reducer(undefined, { type }) === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined when
})
}
/**
* Turns an object whose values are different reducer functions, into a single
* It will call every child reducer, and gather their results
It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions.
It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions.
* reducer functions that need to be combined into one.
One handy way to obtain * it is to use ES6 `import * as reducers` syntax.
* Instead, they should return their initial state
reducers may never return * undefined for any action.
* unrecognized action.
* @returns {Function}
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
// Export the combineReducers method, accepting a reducers object as an argument
export default function combineReducers(reducers) {
// Get the key value of the reducers object
const reducerKeys = Object.keys(reducers)
// Define a final reducers object to be returned
const finalReducers = {}
// Iterate through the keys of this reducers object
for (let i = 0; i < reducerKeys.length; i++) {
// cache each key value
const key = reducerKeys[i]
if (process.env.NODE_ENV ! == 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
// If the value of the corresponding key is a function, the function is cached in finalReducers
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
// Get all the key values of the finalReducers and cache them in the variable finalReducerKeys
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV ! == 'production') {
unexpectedKeyCache = {}
}
// Define a variable to cache the error object
let shapeAssertionError
try {
// Do the error handling, see the assertReducerShape method later for details
// mostly just detects that
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
// Throw an error if there is one
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV ! == 'production') {
// Get the warning message
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
// Define a variable to indicate whether the state has been changed
let hasChanged = false
// Define a variable to cache the changed state
const nextState = {}
// Start iterating through the finalReducerKeys
for (let i = 0; i < finalReducerKeys.length; i++) {
// Get the key value of a valid reducer
const key = finalReducerKeys[i]
// Get the corresponding reducer function based on the key value
const reducer = finalReducers[key]
// Get the corresponding state module based on the key value
const previousStateForKey = state[key]
// Execute the reducer function and get the state of the corresponding module
const nextStateForKey = reducer(previousStateForKey, action)
// If the state obtained is undefined, an error will be thrown
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
// Assign the new state to the module corresponding to the new state, with the key being the key of the current reducer
nextState[key] = nextStateForKey
// Read if the state has changed
hasChanged = hasChanged || nextStateForKey ! == previousStateForKey
}
// If the state has changed, return the new state, otherwise return the original state
return hasChanged ? nextState : state
}
}
次に bindActionCreators.js ファイルを見てみましょう。
まず、actionCreators からですが、これは単純にアクションを作成するためのメソッドです。redux のアクションはオブジェクトですが、このオブジェクトを作成するためによく関数を使用するので、これらの関数が actionCreators となります。
そして、このファイルが行うことは、バインディングに基づいてactionCreatorを自動的にディスパッチすることです。
import warning from '. /utils/warning'
// For each actionCreator method, the execution will result in an action
// The bindActionCreator method returns a method that automatically executes a dispatch
function bindActionCreator(actionCreator, dispatch) {
return (.. .args) => dispatch(actionCreator(actionCreator(.. .args))
}
/**
* Turns an object whose values are action creators, into an object with the
...args. } /** * Turns an object whose values are action creators, into an object with the * same keys, but with every function wrapped into a `dispatch` call so they
* This is just a convenience method, as you can call
This is just a convenience method, as you can call * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
For convenience, you can also pass * `store.
* For convenience, you can also pass a single function as the first argument,
* and get a function in return.
*
* @param {Function|Object} actionCreators An object whose values are action
* One handy way to obtain it is to use ES6 `import * as` syntax.
* You may also pass a single function.
*You may also pass a single function.
You may also pass a single function. * @param {Function} dispatch The `dispatch` function available on your Redux
* store.
* @param
* @returns {Function|Object} The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call.
* function as `actionCreators`, the return value will also be a single
* function.
*/
// Expose this bindActionCreators method externally
export default function bindActionCreators(actionCreators, dispatch) {
// If the actionCreators parameter is a function, call the bindActionCreator method directly
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// Error handling
if (typeof actionCreators ! == 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"? `
)
}
// If actionCreators is an object, then get the keys in the object
const keys = Object.keys(actionCreators)
// Define a cache object
const boundActionCreators = {}
// iterate through each key of actionCreators
for (let i = 0; i < keys.length; i++) {
// get each key
const key = keys[i]
// Get the specific actionCreator method based on each key
const actionCreator = actionCreators[key]
// If actionCreator is a function, call the bindActionCreator method directly, caching the returned anonymous function into the boundActionCreators object
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
} else {
warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'. `)
}
}
// Finally the boundActionCreators object is returned
// Once the user gets this object, he can take out the value corresponding to each key in the object, i.e. each anonymous function, and execute the anonymous function to implement the dispatch function
return boundActionCreators
}
次に、reduxに無限の可能性を与えてくれるapplyMiddleware.jsというファイルを見てみましょう。なぜそうなるのか?下にスクロールするとわかります。
// The code logic in this file is actually quite simple
// First import the compose function, which we'll analyze in more detail in a moment
import compose from '. /compose'
/**
* Creates a store enhancer that applies middleware to the dispatch method * of the Redux store.
* This is handy for a variety of tasks, such as expressing
This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload.
*See
* See `redux-thunk` package as an example of the Redux middleware.
*See the `redux-thunk` package as an example of the Redux middleware.
* Because middleware is potentially asynchronous, this should be the first
* This should be the first store enhancer in the composition chain.
This should be the first * store enhancer in the composition chain.
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
* @param {.
* @param {... Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
// Next, we export the applyMiddleware method, which is often used as an enhancement parameter in createStore
export default function applyMiddleware(.... .middlewares) {
// first return an anonymous function, did you find this function is very similar to createStore?
// Yes, it's actually the same createStore we saw before
return (createStore) => (reducer, preloadedState, enhancer) => {
// First create a store with the original createStore and cache it
const store = createStore(reducer, preloadedState, enhancer)
// Get the original dispatch method in store
let dispatch = store.dispatch
// define an array of execution chains
let chain = []
// cache the original store getState and dispatch methods
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// execute each middleware function and pass middlewareAPI as a parameter to get an array of execution chains
chain = middlewares.map(middleware => middleware(middlewareAPI))
// Pass the execution chain array into the compose method and immediately execute the returned method to get the final wrapped dispatch
// The process is simply that each middleware takes a store.dispatch method, wraps it based on that method, and returns a new dispatch
// This new dispatch is then passed as an argument to the next middleware function, which is then wrapped. Keep looping through this process until you end up with a final dispatch
dispatch = compose(... .chain)(store.dispatch)
// return a store object and override the old dispatch method with the new dispatch method
return {
... .store,
dispatch
}
}
}
ここまででREDUXの大部分を見てもらったので、最後に上記のファイルで使われているcomposeメソッドがどのように実装されているかを見てみましょう。
compose.jsを開くと、実際にはes5の配列のreduceメソッドを使って、このような効果を実現していることがわかります。
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
The rightmost function can take multiple arguments as it provides the signature for * the resulting composite function. * @param {. Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* For example, compose(f, g, h) is identical to doing
For example, compose(f, g, h) is identical to doing * (.... .args) => f(g(h(. .args))).
*/
export default function compose(.. .funcs) {
// Determine if the function array is empty
if (funcs.length === 0) {
return arg => arg
}
// If the function array has only one element, execute it directly
if (funcs.length === 1) {
return funcs[0]
}
// Otherwise, execute each middleware function using the reduce method, taking the return of the previous function as an argument to the next function
return funcs.reduce((a, b) => (. .args) => a(b(. .args)))
}
ははは、以上、本日ご紹介したreduxのソースコード解析でした〜。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
ハートビート・エフェクトのためのHTML+CSS
-
HTML ホテル フォームによるフィルタリング
-
HTML+cssのボックスモデル例(円、半円など)「border-radius」使いやすい
-
HTMLテーブルのテーブル分割とマージ(colspan, rowspan)
-
ランダム・ネームドロッパーを実装するためのhtmlサンプルコード
-
Html階層型ボックスシャドウ効果サンプルコード
-
QQの一時的なダイアログボックスをポップアップし、友人を追加せずにオンラインで話す効果を達成する方法
-
sublime / vscodeショートカットHTMLコード生成の実装
-
HTMLページを縮小した後にスクロールバーを表示するサンプルコード
-
html のリストボックス、テキストフィールド、ファイルフィールドのコード例