前端面试题 - 21. redux原理(附从0实现redux/react-redux源码)

1,780 阅读5分钟

使用原理

Redux是一个用于JavaScript应用程序的可预测状态容器,它是一个单向数据流的框架,用于管理应用程序中的状态(state)。Redux的设计思想是将状态(state)和状态的修改逻辑(reducer)分离,通过中央存储(store)来管理应用程序中的状态(state),使得应用程序的状态更加可预测和可控。 Redux的核心概念包括:action、reducer和store。

  1. action:表示应用程序中的一个事件或操作,它是一个JavaScript对象,包含一个type属性和一些其他的数据。当应用程序中发生某个事件时,我们可以通过dispatch方法来触发一个action,从而通知Redux应用程序中的状态需要发生变化。
  2. reducer:表示应用程序中状态的修改逻辑,它是一个纯函数,接收当前状态(state)和一个action对象作为参数,返回一个新的状态(state)。当应用程序中的状态需要发生变化时,Redux会调用对应的reducer来修改状态。
  3. store:表示应用程序中的状态存储,它是一个JavaScript对象,包含应用程序的当前状态(state)和一些操作状态的方法。当应用程序中的状态需要修改时,我们可以通过调用store的dispatch方法来触发一个action,并通过reducer来修改应用程序的状态。

Redux的工作流程如下:

  1. 应用程序中的某个事件触发一个action。
  2. Redux的store接收到action,调用对应的reducer来修改应用程序的状态。
  3. 修改后的状态被存储在Redux的store中,所有订阅store的组件都会收到通知,并进行相应的更新。 通过这样的方式,Redux实现了一个单向数据流的框架,使得应用程序的状态更加可预测和可控。同时,Redux还提供了一些工具和中间件(middleware),用于处理异步请求、日志记录、调试等功能,使得Redux可以满足各种复杂应用程序的需求。

Redux 有三大原则:

  • store: 单一
  • state: 只读
  • reducer: 纯函数

示例

以下是一个简单的Redux示例:

  1. 首先,我们需要定义应用程序的初始状态(state):
const initialState = {
  count: 0
};
  1. 接着,我们需要定义一个reducer来处理状态的修改逻辑,并将其连接到Redux的store中:
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}
const store = createStore(counterReducer);

在这个示例中,我们定义了一个counterReducer函数,它接收当前状态(state)和一个action对象作为参数,根据action的type属性来执行不同的逻辑,并返回一个新的状态。我们还通过createStore方法创建了一个Redux的store,并将counterReducer函数连接到了store中。

  1. 然后,我们可以在应用程序中触发一个action,从而修改应用程序的状态:
store.dispatch({ type: 'INCREMENT' }); // 状态变为 { count: 1 }
store.dispatch({ type: 'INCREMENT' }); // 状态变为 { count: 2 }
store.dispatch({ type: 'DECREMENT' }); // 状态变为 { count: 1 }

在这个示例中,我们通过调用store的dispatch方法来触发了三个不同的action,从而修改了应用程序的状态。每次触发一个action,都会调用对应的reducer来修改状态,并将新的状态存储在Redux的store中。

  1. 最后,我们可以通过订阅store的变化来获取当前状态,并对应用程序进行更新:
function render() {
  const state = store.getState();
  console.log(state.count);
}
store.subscribe(render);

在这个示例中,我们定义了一个render函数,它会根据当前状态来更新应用程序的UI。我们还通过调用store的subscribe方法来订阅store的变化,使得每当应用程序的状态发生变化时,都会调用render函数进行更新。 通过这个示例,我们可以看到Redux的工作流程和应用方式,以及如何通过Redux来管理应用程序的状态。

实现原理

实现部分参考:www.imooc.com/read/72/art…

image.png

redux

  • createStore 创建事件监听。参考EventEmiter

    • getState - 返回store
    • dispatch - store修改,listener执行。为避免并发,需要加锁
    • subscribe - listener添加监听方法
    • unsubscribe - listener清理掉
  • combineReducers 对多个reduce合并,便于创建store。对象遍历

  • applyMiddleware 合并多个中间件,给每个传参,最后增强dispatch。

  • bindActionCreator 绑定action和dispatch。子组件

// 这里需要对参数为0或1的情况进行判断
const compose = (...funcs) => {
    if (!funcs) {
        return args => args
    }
    if (funcs.length === 1) {
        return funcs[0]
    }
    return funcs.reduce((f1, f2) => (...args) => f1(f2(...args)))
}

const bindActionCreator = (action, dispatch) => {
    return (...args) => dispatch(action(...args))
}

const createStore = (reducer, initState, enhancer) => {
    if (!enhancer && typeof initState === "function") {
        enhancer = initState
        initState = null
    }
    if (enhancer && typeof enhancer === "function") {
        return enhancer(createStore)(reducer, initState)
    }
    let store = initState, 
        listeners = [],
        isDispatch = false;
    const getState = () => store
    const dispatch = (action) => {
        if (isDispatch) return action
        // dispatch必须一个个来
        isDispatch = true
        store = reducer(store, action)
        isDispatch = false
        listeners.forEach(listener => listener())
        return action
    }
    const subscribe = (listener) => {
        if (typeof listener === "function") {
            listeners.push(listener)
        }
        return () => unsubscribe(listener)
    }
    const unsubscribe = (listener) => {
        const index = listeners.indexOf(listener)
        listeners.splice(index, 1)
    }
    return {
        getState,
        dispatch,
        subscribe,
        unsubscribe
    }
}

const applyMiddleware = (...middlewares) => {
    return (createStore) => (reducer, initState, enhancer) => {
        const store = createStore(reducer, initState, enhancer);
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        let chain = middlewares.map(middleware => middleware(middlewareAPI))
        store.dispatch = compose(...chain)(store.dispatch)
        return {
          ...store
        }
      }
}

const combineReducers = reducers => {
    const finalReducers = {},
        nativeKeys = Object.keys
    nativeKeys(reducers).forEach(reducerKey => {
        if(typeof reducers[reducerKey] === "function") {
            finalReducers[reducerKey] = reducers[reducerKey]
        }
    })
    return (state, action) => {
        const store = {}
        nativeKeys(finalReducers).forEach(key => {
            const reducer = finalReducers[key]
            const nextState = reducer(state[key], action)
            store[key] = nextState
        })
        return store
    }
}

react-redux

一共提供了两个 API,分别是 connect 和 Provider

  1. Provider:将 store 通过 Context 传给后代组件,注册对 store 的监听;
  2. connect:一旦 store 变化就会执行 mapStateToProps 和 mapDispatchToProps 获取最新的 props 后,将其传给子组件。
// Provider
ReactDOM.render({
    <Provider store={store}></Provider>,
    document.getElementById('app')
})
// connect
@connect(mapStateToProps, mapDispatchToProps)
class App extends Component {}
  • provider

实现个组件的发布订阅,并跟store联系起来。

class Subscription {
    constructor(store) {
        this.store = store;
        this.listeners = [this.handleChangeWrapper];
    }
    notify = () => {
        this.listeners.forEach(listener => {
            listener()
        });
    }
    addListener(listener) {
        this.listeners.push(listener);
    }
    // 监听 store
    trySubscribe() {
        this.unsubscribe = this.store.subscribe(this.notify);
    }
    // onStateChange 需要在组件中设置
    handleChangeWrapper = () => {
        if (this.onStateChange) {
          this.onStateChange()
        }
    }
    unsubscribe() {
        this.listeners = null;
        this.unsubscribe();
    }
}

在store变化的时候生成新的发布订阅,在组件useEffect监听它,发生变化后就跟组件的联系起来。

const Provider = ({ store, children }) => {
    const contextValue = useMemo(() => {
        const subscription = new Subscription(store);
        return {
            store,
            subscription
        }
    }, [store]);
    // 监听 store 变化
    useEffect(() => {
        const { subscription } = contextValue;
        subscription.trySubscribe();
        return () => {
            subscription.unsubscribe();
        }
    }, [contextValue]);
    return (
        <ReactReduxContext.Provider value={contextValue}>
            {children}
        </ReactReduxContext.Provider>
    )
}

在connect中套上组件,当组件的props,以及store变化就生成新的props和自定义属性。并且触发组件变化。

const connect = (mapStateToProps, mapDispatchToProps) => {
    return (WrappedComponent) => {
        return (props) => {
            const { store, subscription } = useContext(ReactReduxContext);
            const [count, setCount] = useState(0)
            useEffect(() => {
                subscription.onStateChange = () => setCount(count + 1)
            }, [count])
            const newProps = useMemo(() => {
                const stateProps = mapStateToProps(store.getState()),
                    dispatchProps = mapDispatchToProps(store.dispatch);
                return {
                    ...stateProps,
                    ...dispatchProps,
                    ...props
                }
            }, [props, store, count])
            return <WrappedComponent {...newProps} />
        }
    }
}