redux

作者: Zindex | 来源:发表于2022-07-24 22:33 被阅读0次

    redux重记

    入门

    redux入门先要使用两个东西,一个是 reducer,一个是 store。
    先说 reducer,是一个会被 redux store 调用的一个函数,会接到两个参数:state 和 action。
    然后根据 action 的类型和 action 携带的参数来执行一些修改state的操作,我们先不管 action

    const reducer = (state, action) => {
        switch (action.type) {
            case '???':
                return '本次reducer处理完的数据'
            default:
                return 'reducer默认数据'
        }
    }
    export default reducer
    

    store 的建立必须依赖一个 reducer,使用 createStore 这个来自 redux 库的 api,然后传入一个 reducer ,就会生成一个 store,这个store,就是给其他组件用的大仓库。

    import { legacy_createStore as createStore } from "redux";
    import reducer from './reducer'
    const store = createStore(reducer)
    export default store
    

    我们找个地方看一下 store 长啥样,控制台输出可以看到,store 带有dispatch,getState等方法
    [图片上传失败...(image-4d903-1658743707093)]
    调用一下 getState ,就会发现得到 reducer 的默认值

    import store from '../../store'
    console.log(store.getState())       //输出    'reducer默认数据'
    

    此时意识到 reducer 那个 default 的 return 就是 store 的初始值,我们改写一下 store

    const defaultState = {
        count: 0
    }
    const reducer = (state = defaultState, action) => {
        switch (action.type) {
            case '???':
                return '本次reducer处理完的数据'
            default:
                return state
        }
    }
    export default reducer
    

    ✳补充知识:第一次 createStore 调用 reducer的时候,state 是 undefined ,action 是名为 {
    type: "@@redux/INITk.0.v.x.n.o"} 的一个对象,因为这个 state 实际上是 preState即 前一个状态

    此时在组件中调用 store.getState() 就可以读到 store 里面的数据了。

    import store from '../../store'
    ...
    <h1>当前求和为:{store.getState().count}</h1>
    

    入门阶段先告一段落,我们可以知道,store貌似什么都没有,实际数据都在 reducer 上,这点很重要。

    上手

    此时我们知道了 reducer 才是卖力工作那个,我们看回来那个入门阶段没管的 action 与 store 的 dispatch
    action 是一个我们约定好的对象,包含 type 与 data 给 reducer 处理。怎么给?在组件调用 store.dispatch(action) 这种形式给

    import store from '../../store'
    某个方法 = ()=>{
        store.dispatch({
            type: '派发的action名',
            data: reducer 可以用的数据
        })
    }
    实例:
    increment = () => {
        let data = this.select.value
        store.dispatch({
            type: 'increment',
            data
        })
    }
    

    此时我们回到 reducer 中处理,注意 return 的值会修改整个原始 state 对象,我们用合并方式处理

    const defaultState = {
        count: 0
    }
    const reducer = (state = defaultState, action) => {
        switch (action.type) {
            case 'increment':
                console.log(state);
                return { ...state, count: state.count + action.data * 1 }
            default:
                return state
        }
    }
    export default reducer
    

    此时又可以看到state 确实变了,但页面没有更新,那是因为想要页面响应式,必须调用 setState,forceUpdate 等方法,如何检测到 store 里的变化后就调用页面相应呢?
    store 提供了 subscribe 这个 api,这个 api 会接受一个回调函数,这个回调函数会在store里的数据变化时调用。我们现在可以让组件在挂载时就让store检测到变化就调用 setState({}) 假动作演一下组件

    componentDidMount() {
        store.subscribe(() => {
            this.setState({})
        })
    }
    

    当然可以狠一点直接放去 index.js 的 ReactDOM.render 外面

    上手完善

    我们已经了解了 reducer 与 store 与 action 了,实际工作上,我们还会把 action 作为一个单独文件提取出来,并把 action 的 type 也提取出来用变量来代替字符串

    //type 提取 constant.js 文件
    export const INCREMENT = 'increment'
    export const DECREMENT = 'decrement'
    
    //action 提取 action.js 文件
    import * as actionType from './constant'
    export const createIncrementAction = (data) => ({
        type: actionType.INCREMENT,
        data
    })
    export const createDecrementAction = (data) => ({
        type: actionType.DECREMENT,
        data
    })
    

    组件使用的时候

    import * as actionCreators from '../../store/action'
    increment = () => {
        let data = this.select.value
        store.dispatch(actionCreators.createIncrementAction(data))
    }
    

    进阶 异步action

    action 除了可以是对象类型,还可以是函数类型,一般对象类型的是同步 action ,函数类型就是异步 action。
    我们创建一个异步 action

    import * as actionType from './constant'
    import store from './index'
    export const createIncrementAction = (data) => ({
        type: actionType.INCREMENT,
        data
    })
    export const createIncrementAsyncAction = (data, time) => {
        return () => {
            setTimeout(() => {
                store.dispatch(createIncrementAction(data))
            }, time)
        }
    }
    

    然后给组件派发

    asyncIncrement = () => {
        const { value } = this.select
        store.dispatch(actionCreators.createIncrementAsyncAction(value, 500))
    }
    

    然后会发现报错,说如果想使用非对象类型的 action ,需要使用中间件比如 redux-thunk
    npm i redux-thunk 接下来就是死记硬背了,在 createStore 时传入第二个参数,该参数需要从 redux 引入 applyMiddleware 和从 redux-thunk 引入 thunk 最后组合成 applyMiddleware(thunk)

    import { legacy_createStore as createStore, applyMiddleware } from "redux";
    import thunk from "redux-thunk";
    import reducer from './reducer'
    const store = createStore(reducer, applyMiddleware(thunk))
    export default store
    

    然后我们仔细看一下组件和action的代码会发现调用了两次 store.dispatch,实际上在 action 返回的函数中我们可以接到一个 dispatch,就不用引入 store 再 dispatch 了

    import * as actionType from './constant'
    export const createIncrementAction = (data) => ({
        type: actionType.INCREMENT,
        data
    })
    export const createIncrementAsyncAction = (data, time) => {
        return (dispatch) => {
            setTimeout(() => {
                dispatch(createIncrementAction(data))
            }, time)
        }
    }
    

    react-redux

    react-redux 将使用 redux 的组件分离成容器组件和 UI 组件,UI 组件不处理任何与 store 相关的操作。全部交给容器组件处理。容器通过 props 把数据传给 UI 组件。创建容器组件需要借助 react-redux 上的 connect,在第二个括号放入 UI 组件

    class Count extends Component{
        ...
    }
    export default connect()(Count)
    

    此时我们使用容器组件,会发现报错说找不到 store,我们需要在使用容器组件时通过props传入store

    import Count from './component/Count'
    import store from './store'
    export default class App extends Component {
        render() {
            return (
                <div>
                    <Count store={store} />
                </div>
            )
        }
    }
    

    容器通过 connect 的第一个参数把 store 的 state 传给 UI 组件,第一个参数叫 mapStateToProps ,是一个接收到 store 的state为形参的函数。返回值的对象就是作为 props 传给 UI 组件的对象

    const mapStateToProps = (state)=>{
        return {
            count: state.count
        }
    }
    

    容器通过 connect 的第二个参数把 store 的 dispatch 传递给 UI 组件,第二个参数叫 mapDispatchToProps,是一个接收 store 的 dispatch 为形参的函数,返回值的对象就是作为 props 传给 UI 组件的对象

    const mapDispatchToProps = (dispatch) => {
        return {
            increment: value => { dispatch(actionCreators.createIncrementAction(value)) },
            decrement: value => { dispatch(actionCreators.createDecrementAction(value)) }
        }
    }
    

    此时只需要 export default connect(mapStateToProps, mapDispatchToProps)(index) 就好了

    简写优化

    可以看到 mapStateToProps 和 mapDispatchToProps 最终返回的都是一个对象,可以触发直接返回对象的简写形式,但针对 mapDispatchToProps 还有一项优化,那就是 mapDispatchToProps 不仅可以写成方法,还可以写成对象。
    那么写成对象 dispatch去哪了?参数去哪传?dispatch 的话 react-redux 会自动帮你调用,参数直接往 actionCreate 里传

    const mapStateToProps = (state) => ({
        count: state.count
    })
    const mapDispatchToProps = {
        increment: actionCreators.createIncrementAction,
        decrement: actionCreators.createDecrementAction
    }
    export default connect(mapStateToProps, mapDispatchToProps)(index)
    

    省略 store.subscribe

    我们在 redux 中还需要手动调用 store.subscribe ,在 react-redux 中发现不使用也会自动更新视图,实际上是因为 react-redux 之所以分离 容器组件和 UI组件,就是因为 connect 会自动更新 UI 组件

    Provider

    我们在使用容器组建的时候会传入一个store={store},如果组件一多,岂不是每个组件都要传一次?
    此时我们需要使用 Provider ,外面包一层 <Provider store={store}> 即可,这个组件是从 react-redux 中引入的,我们可以用它直接包住 App 组件

    import store from "./store";
    import { Provider } from "react-redux";
    const root = ReactDOM.createRoot(document.getElementById('root'))
    root.render(
        <React.StrictMode>
            <Provider store={store}>
                <App />
            </Provider>
        </React.StrictMode>
    )
    

    也可以在项目中看到在 App 组件中用 Provider 包住 children

    render() {
        return (
            <Provider store={store}>
                <React.Fragment>
                    {this.state.env && this.props.children}
                </React.Fragment>
            </Provider>
        );
    }
    

    处理多个 store ,使用 combineReducers

    使用完 combineReducers 后,我们来看一下 store 总文件。

    import { legacy_createStore as createStore, applyMiddleware, combineReducers } from "redux";
    import thunk from "redux-thunk";
    import countReducer from '../component/Count/store/reducer'
    import personReducer from '../component/Person/store/reducer'
    const reducer = combineReducers({
        countReducer,
        personReducer
    })
    const store = createStore(reducer, applyMiddleware(thunk))
    export default store
    

    在其他容器组件中,读取 store 的内容就变成了

    const mapStateToProps = (state) => ({
        persons: state.personReducer.persons,
        count: state.countReducer.count
    })
    

    mapDispatchToProps 没变,因为 action 的生成不依赖 store。
    工作项目中还能看到 immutable 配合 redux-immutable 的,combineReducers 从 redux-immutable中引入。
    实际上还能把 reducer 单独提成一个文件,在 store 引入就行。

    import { combineReducers } from 'redux'
    import countReducer from '../component/Count/store/reducer'
    import personReducer from '../component/Person/store/reducer'
    export default combineReducers({
        countReducer,
        personReducer
    })
    

    配置 redux 开发者工具

    store 中

    import {composeWithDevTools} from 'redux-devtools-extension'
    export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
    

    纯函数

    只要是同样的输入,必定得到同样的输出。遵循以下约束

    1. 不得改写原参数数据
    2. 不会产生任何副作用,例如网络请求,输入输出设备
    3. 不能调用 Date.now() 或 Math.random() 等不纯的方法

    redux 中的 reducer 必须是一个纯函数

    相关文章

      网友评论

          本文标题:redux

          本文链接:https://www.haomeiwen.com/subject/mpbwirtx.html