从 0 开始搭建 React 框架

webpack 配置

不再赘述,可参考前三个文章(wenpack5 基本使用 1 - 3

使用 react

安装 react、react-dom、@babel/preset-react

yarn add react react-dom @babel/preset-react
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>
<body>
    <div id="root">div>
body>
html>
// index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root'),
    root = createRoot(container);

root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);
// class 组件
import React, { PureComponent } from 'react';

class App extends PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            name: 'zhangsan',
            age: 20
        };

        this.changeToLisi = this.changeToLisi.bind(this);
    }

    changeToLisi() {
        this.setState({
            name: 'lisi'
        });
    }

    changeAge = () => {
        this.setState({
            age: 30
        });
    }

    render() {
        return <div>
            <p>Tis is a class component.</p>
            <p>姓名:{ this.state.name }</p>
            <p>年龄:{ this.state.age }</p>
            <button onClick={this.changeToLisi}>切换名字</button>
             <button onClick={this.changeAge}>切换年龄</button>
        </div>;
    }
}

export default App;
// 函数组件
import React from 'react';

const App = () => {
    return <p>This is react compoment</>;
};

export default App;
// .babelrc
{
    "presets": [
    	"@babel/preset-env", // 不配置这个 class 组件写箭头函数会报错
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-transform-runtime"
    ]
}
module.exports = {
	module: {
        rules: [
            // 处理 js,将 es6 转为 es5
            {
                test: /\.(js|jsx)$/, // 追加 jsx
                exclude: /node_modules/,
                use: ['babel-loader']
            }
        }
    }
};

配置 eslint 和 prettier

webpack 基本配置中已安装 eslint、eslint-webpack-plugin、@babel/eslint-parser、eslint-plugin-import、stylelint-webpack-plugin

安装 react 相关的 eslinteslint-plugin-react、eslint-plugin-react-hooks

yarn add eslint-plugin-react、eslint-plugin-react-hooks

安装 prettier、eslint-config-prettier、eslint-plugin-prettier

yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
// .prettierrc.js
module.exports = {
    // 行宽 default:80
    printWidth: 110,
    // tab 宽度 default:2
    tabWidth: 4,
    // 使用 tab 键 default:false
    useTabs: false,
    // 语句行末是否添加分号 default:true
    semi: true,
    // 是否使用单引号 default:false
    singleQuote: true,
    // 对象需要引号在加 default:"as-needed"
    quoteProps: 'as-needed',
    // jsx单引号 default:false
    jsxSingleQuote: true,
    // 最后一个对象元素加逗号 default:"es5"
    // trailingComma: 'es5',
    trailingComma: 'none',
    // 在对象字面量声明所使用的的花括号后({)和前(})输出空格 default:true
    bracketSpacing: true,
    // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素)。default:false
    jsxBracketSameLine: true,
    // (x) => {} 是否要有小括号 default:"always"
    arrowParens: 'avoid',
    // default:0
    rangeStart: 0,
    // default:Infinity
    rangeEnd: Infinity,
    // default:false
    insertPragma: false,
    // default:false
    requirePragma: false,
    // 不包装 markdown text default:"preserve"
    proseWrap: 'never',
    // HTML空白敏感性 default:"css"
    htmlWhitespaceSensitivity: 'strict',
    // 在 *.vue 文件中 Script 和 Style 标签内的代码是否缩进 default:false
    vueIndentScriptAndStyle: true,
    // 末尾换行符 default:"lf"
    endOfLine: 'auto',
    // default:"auto"
    embeddedLanguageFormatting: 'auto',
    overrides: [
        {
            files: '*.md',
            options: {
                tabWidth: 2
            }
        }
    ]
};
module.exports = {
    root: true,
    env: {
        browser: true,
        node: true
    },
    globals: {},
    extends: [
        'eslint:recommended',
        'plugin:prettier/recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended'
    ],
    plugins: ['import', 'prettier'],
    parserOptions: {
        parser: '@babel/eslint-parser',
        sourceType: 'module',
        ecmaVersion: 2021,
        ecmaFeatures: {
            jsx: true,
            experimentalObjectRestSpread: true
        }
    },
    rules: [
    	// ...
    ]
};

配置 redux

官方已推荐使用 @reduxjs/toolkit(地址:https://cn.redux.js.org/introduction/why-rtk-is-redux-today)

使用传统 redux

安装 redux、react-redux、redux-thunk

yarn add redux react-redux redux-thunk
// src/store-redux/index.js
import {createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { thunk } from 'redux-thunk';
import { reducer as page1Reducer } from '@pages/page1/reducer';


const combinedReducerObj = {
    page1: combineReducers(page1Reducer)
};

const store = createStore(
    combineReducers(combinedReducerObj),
    compose(applyMiddleware(thunk)
));

export default store;
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store-redux/index';
import Page1 from '@pages/page1/index.jsx';

const App = () => {
    return <Provider store={store}>
        <Page1 />
    </Provider>;
};

export default App;
// src/pages/page1/reducer.js

// 定义 actionType
const ADD_TODO = 'ADD_TODO';
const TODO_TOGGLED = 'TODO_TOGGLED';
const SET_DATA = 'SET_DATA';

// 定义 reducer
const todos = (state = [{ id: 1, text: 'zhangsan', completed: false }], action) => {
    switch (action.type) {
        case ADD_TODO:
            return state.concat({
                id: action.payload.id,
                text: action.payload.text,
                completed: false
            });
        case TODO_TOGGLED:
            return state.map(todo => {
                if (todo.id !== action.payload.id) return todo

                return {
                    ...todo,
                    completed: !todo.completed
                }
            });
        default:
            return state
    }
};

const dataList = (state = [], action) => {
    if (action.type === SET_DATA) {
        return action.payload;
    }

    return state;
};

export const reducer = {
    todos,
    dataList
};

export const actionTypes = {
    ADD_TODO,
    TODO_TOGGLED,
    SET_DATA
};
// src/pages/page1/actions.js
import { actionTypes } from './reducer';
import { getDataService } from './service';

// 同步 action
const addTodo = ({ text }) => {
    return {
        type: actionTypes.ADD_TODO,
        payload: {
            text,
            id: Math.random()
        }
    };
};

const todoToggled = ({ id }) => {
    return {
        type: actionTypes.TODO_TOGGLED,
        payload: {
            id
        }
    };
};

const setDataList = data => {
    return {
        type: actionTypes.SET_DATA,
        payload: data
    };
};

// 异步 action
const thunkGetDataList = (data) => {
    return (dispatch, getState) => {
        // dispatch(addTodo({ text: 'lisi'}));

        const page1State = getState().page1;

        console.log(page1State);

        getDataService().then(res => {
            dispatch(setDataList(res.data))
        });
    };
};

export default {
    // 同步 action
    addTodo,
    todoToggled,

    // 异步 action
    setDataList,
    thunkGetDataList
};
// src/pages/page1/service.js
// 模拟接口获取数据
export const getDataService  = (params) => {
    console.log(params);

    return new Promise((resolve, reject) => {
        const res= {
            data: [
                {
                    id: 1,
                    name: 'zhangsan',
                    age: 20
                },
                {
                    id: 2,
                    name: 'lisi',
                    age: 30
                }
            ]
        };

        setTimeout(() => {
            resolve(res);
        }, 3000)
    });
};
// src/pages/page1/index.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import actions from './actions';

const Page1 = ({
        todos,
        dataList,

        addTodo,
        getData

}) => {
    useEffect(() => {
        getData();
    }, []);

    return <div>
        <p>This is App Component.</p>


        {
            dataList.map((item, index) => {
                return (
                    <div key={item.id}>
                        <p>id: {item.id};姓名: {item.name};年龄:{item.age}</p>
                    </div>
                );
            })
        }

        <p>-------------------------------------------</p>

        {
            todos.map((item, index) => {
                return (
                    <div key={item.id}>
                        <p>id: {item.id};text: {item.text};compoleted: {item.completed}</p>
                    </div>
                );
            })
        }

        <button onClick={addTodo}>添加</button>

    </div>;
};

const mapStateToProps = (state, props) => {
        // let stateCommon = state.common;
        const statePage1 = state.page1;

        return {
            todos: statePage1.todos,
            dataList: statePage1.dataList
        };
    },

    mapDispatchToProps = (dispatch, props) => {
        return {
            addTodo: () => {
                dispatch(actions.addTodo({ id: 4, text: 'zzz' }));
            },
            getData: () => {
                dispatch(actions.thunkGetDataList());
            }
        };
    };

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(React.memo(Page1));

使用 @reduxjs/toolkit

安装 @reduxjs/toolkit、react-redux

yarn add @reduxjs/toolkit react-redux

官方使用方案

创建 store
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import commonReducer from './common/reducer';
import HomeReducer from '@pages/home/reducer';

export const store = configureStore({
    // 合并所有的 reducer
    reducer: {
        common: commonReducer,
        home: HomeReducer
    },
    // 解决 redux 无法序列化 数据时的报错
    middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: false })
});

export default store.dispatch;
创建 reducerSlice
  • configureStore 创建 store
  • 在根节点通过 Provider 传入 store
  • createSlice 创建 reducer 切片:传入 name、初始状态、定义 reducer,生成 reduceractions
  • 页面上使用:通过 useSlector 获取状态;useDispatch 分发 action
// src/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
    name: 'page2', // 命名空间
    initialState: {
        counter: 0,
        todoList: []
    }, // 初始值
    reducers: {
        counterIncrement: state => {
            // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
            // 并不是真正的改变状态值,因为它使用了 Immer 库
            // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
            // 不可变的状态
            state.counter += 1;
        },
        counterDecrement: state => {
            state.counter -= 1;
        },
        counterIncrementByAmount: (state, action) => {
            state.counter += action.payload;
        },
        todoListAdded(state, action) {
            state.todoList.push({
                id: action.payload.id,
                text: action.payload.text,
                completed: false
            });
        },
        todoListToggled(state, action) {
            const todoItem = state.todoList.find(todo => todo.id === action.payload);

            todoItem.completed = !todoItem.completed;
        }
    }
});

// 普通 action
export const {
    counterDecrement,
    counterIncrement,
    counterIncrementByAmount,
    todoListAdded,
    todoListToggled
} = todosSlice.actions;

export const actions = todosSlice.actions;

// reducer
export default todosSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import page2Reducer from '@pages/page2/reducer';

export const store = configureStore({
    // 传入所有的 reducer, configureStore 会帮我们自动合并 reducer
    reducer: {
        page2: page2Reducer
    }
});
// src/pages/page2/index.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { actions } from './reducer';

const Page2 = () => {
    const { counter, todoList } = useSelector(state => state.page2), // 获取状态
        dispatch = useDispatch();

    return <div>
        <p>This is App Component.</p>

        <p>计数器:{ counter }</p>

        {
            todoList.map((item, index) => {
                return (
                    <div key={item.id}>
                        <p>id: {item.id}</p>
                        <p>text: {item.text}</p>
                        <p>completed: {String(item.completed)}</p>
                    </div>
                );
            })
        }
        <button onClick={() => dispatch(actions.counterIncrement())}>计数器添加</button>
        <button onClick={() => dispatch(actions.counterDecrement())}>计数器减少</button>
        <button onClick={() => dispatch(actions.counterIncrementByAmount(5))}>计数器每次+5</button>
        <button onClick={() => dispatch(actions.todoListAdded({ id: 1, text: 'zhangsan' }))}>添加</button>
        <button onClick={() => dispatch(actions.todoListToggled(1))}>切换状态</button>
    </div>;
};

export default React.memo(Page2);
处理异步操作 createAsyncThunk

传统 redux 需要使用安装 redux-thunk
redux toolkit 使用 createAsyncThunk API 简化异步调用。

// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';

// 模拟接口获取数据
const getDataService  = (params) => {
    console.log(params);

    return new Promise((resolve, reject) => {
        const res= {
            data: [
                {
                    id: 1,
                    name: 'zhangsan',
                    age: 20
                },
                {
                    id: 2,
                    name: 'lisi',
                    age: 30
                }
            ]
        };

        setTimeout(() => {
            resolve(res);
        }, 3000)
    });
};

// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {
    // 通过 payload 可以接收数据
    const { params } = payload;

    // 通过 getState 可以获取 store
    const { counter } = getState().page2;
    console.log(counter);

    // 通过 dispatch 可以分发 action
    // 在 createAsyncThunk 可以 dispatch 普通的 action
    dispatch(actions.counterIncrement());
    // 在 createAsyncThunk 也可以 dispatch 异步 action
    dispatch(asyncSetLocale({ locale: 'pl' }));

    const response = await getDataService(params);

    // 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
    return {
        dataList: response.data
    };
});

export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {
        return {
            locale: payload.locale,
            menuList: [{
                id: 11,
                menuName: '电站'
            }]
        };
    });


// 导出异步 actions
const asyncActions = {
    asyncGetData,
    asyncSetLocale
};

export default asyncActions;
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';

const todosSlice = createSlice({
    name: 'page2', // 命名空间
    initialState: {
        counter: 0,
        todoList: [],
        dataList: [], // 接口返回的数据
        locale: 'en',
        menuList: []
    }, // 初始值
    reducers: {
        counterIncrement: state => {
            // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
            // 并不是真正的改变状态值,因为它使用了 Immer 库
            // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
            // 不可变的状态
            state.counter += 1;
        },
        counterDecrement: state => {
            state.counter -= 1;
        },
    },
    extraReducers: builder => {
        builder.addCase(asyncActions.asyncGetData.fulfilled, (state, action) => {
            // 接收 createAsyncThunk 中 return 的数据
            const { dataList } = action.payload;

            // 设置 state
            state.dataList = dataList;
        });

        builder.addCase(asyncActions.asyncSetLocale.fulfilled, (state, action) => {
            const {
                locale,
                menuList
            } = action.payload;

            state.locale = locale;
            state.menuList = menuList;
        });
    }
});

// 普通 action
export const {
    counterDecrement,
    counterIncrement,
} = todosSlice.actions;

export const actions = todosSlice.actions;

// reducer
export default todosSlice.reducer;
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import asyncActions from './asyncAtions';

const Home = () => {
    const { counter, todoList, locale, menuList, dataList } = useSelector(state => state.page2), // 获取状态
        dispatch = useDispatch();

    useEffect(() => {
    	// 调用异步 action
        dispatch(asyncActions.asyncGetData({ query: '参数1' }));
    }, []);

    return <div>
        <p>This is App Component.</p>

        {/* 

计数器:{ counter }

*/
} <p>当前语言: { locale }</p> { dataList.map((item, index) => { return ( <div key={item.id}> <p>id: {item.id};姓名: {item.name};年龄:{item.age}</p> </div> ); }) } <p>-------------------------------------------</p> { menuList.map((item, index) => { return ( <div key={item.id}> <p>id: {item.id};菜单: {item.menuName}</p> </div> ); }) } </div>; }; export default React.memo(Home);

简化 reducerSlice 和 createAsyncThunk

简化 createAsyncThunk

由于 createAsyncThunk 内部就可以直接 dispatch 一个普通的 action,就可以直接在这里面通过 dispatch(actions.setXxx('zhangsan')) 的方式修改 state,不需要 return 数据,这样就不用在 createSlice 中编写 extraReducers

// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';

// 模拟接口获取数据
const getDataService  = (params) => {
    console.log(params);

    return new Promise((resolve, reject) => {
        const res= {
            data: [
                {
                    id: 1,
                    name: 'zhangsan',
                    age: 20
                },
                {
                    id: 2,
                    name: 'lisi',
                    age: 30
                }
            ]
        };

        setTimeout(() => {
            resolve(res);
        }, 3000)
    });
};

// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {
    // 通过 payload 可以接收数据
    const { params } = payload;

    // 通过 getState 可以获取 store
    const { counter } = getState().page2;
    console.log(counter);

    // 通过 dispatch 可以分发 action
    // 在 createAsyncThunk 可以 dispatch 普通的 action
    dispatch(actions.counterIncrement());
    // 在 createAsyncThunk 也可以 dispatch 异步 action
    dispatch(asyncSetLocale({ locale: 'pl' }));

    const response = await getDataService(params);

    // 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
    // return {
    //     dataList: response.data
    // };
    // 直接在这里 setState
    dispatch(actions.setDataList(response.data));
});

export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {
        // return {
        //     locale: payload.locale,
        //     menuList: [{
        //         id: 11,
        //         menuName: '电站'
        //     }]
        // };
        // 直接在这里 setState
        dispatch(actions.setLocale(payload.locale));
        dispatch(actions.setMenuList([{ id: 11, menuName: '电站' }]));
    });


// 导出异步 actions
const asyncActions = {
    asyncGetData,
    asyncSetLocale
};

export default asyncActions;
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';

const todosSlice = createSlice({
    name: 'page2', // 命名空间
    initialState: {
        counter: 0,
        todoList: [],
        dataList: [], // 接口返回的数据
        locale: 'en',
        menuList: []
    }, // 初始值
    reducers: {
        counterIncrement: state => {
            // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
            // 并不是真正的改变状态值,因为它使用了 Immer 库
            // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
            // 不可变的状态
            state.counter += 1;
        },
        counterDecrement: state => {
            state.counter -= 1;
        },
        setDataList(state, action) {
            state.dataList = action.payload;
        },
        setLocale(state, action) {
            state.locale = action.payload;
        },
        setMenuList(state, action) {
            state.menuList = action.payload;
        }
    },
    // extraReducers: builder => {
    //     builder.addCase(asyncActions.asyncGetData.fulfilled, (state, action) => {
    //         // 接收 createAsyncThunk 中 return 的数据
    //         const { dataList } = action.payload;
    //
    //         // 设置 state
    //         state.dataList = dataList;
    //     });
    //
    //     builder.addCase(asyncActions.asyncSetLocale.fulfilled, (state, action) => {
    //         const {
    //             locale,
    //             menuList
    //         } = action.payload;
    //
    //         state.locale = locale;
    //         state.menuList = menuList;
    //     });
    // }
});

export const actions = todosSlice.actions;

// reducer
export default todosSlice.reducer;
// src/pages/page2/index.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import asyncActions from './asyncAtions';

const Home = () => {
    const { counter, todoList, locale, menuList, dataList } = useSelector(state => state.page2), // 获取状态
        dispatch = useDispatch();

    useEffect(() => {
        dispatch(asyncActions.asyncGetData({ query: '参数1' }));
    }, []);

    return <div>
        <p>This is App Component.</p>

        {/* 

计数器:{ counter }

*/
} <p>当前语言: { locale }</p> { dataList.map((item, index) => { return ( <div key={item.id}> <p>id: {item.id};姓名: {item.name};年龄:{item.age}</p> </div> ); }) } <p>-------------------------------------------</p> { menuList.map((item, index) => { return ( <div key={item.id}> <p>id: {item.id};菜单: {item.menuName}</p> </div> ); }) } </div>; }; export default React.memo(Home);

如果你还是想在 cerateAsyncThunk 中通过 return 得形式返回状态。那么可以优化 extraReducers 的写法,前提是 return 一个对象。

// src/store/createExtraReducers.js
export const createExtraReducers = (actions = {}) => {
    const extraReducers = {};

    for (const action in actions) {
        if (actions.hasOwnProperty(action)) {
            // action 的异步任务执行完成才修改 state
            extraReducers[actions[action].fulfilled] = (state, { payload }) => {
                for (const key in payload) {
                    if (Object.hasOwnProperty.call(payload, key)) {
                        if (key !== 'callback') {
                            state[key] = payload[key];
                        } else {
                            payload.callback && payload.callback();
                        }
                    }
                }
            };
        }
    }

    return extraReducers;
};
// src/pages/page2/reducer.js
import { createSlice } from '@reduxjs/toolkit';
import asyncActions from './asyncAtions';
import { createExtraReducers } from '@store/createExtraReducers.js';

const extraReducers = createExtraReducers(asyncActions);

const todosSlice = createSlice({
    name: 'page2', // 命名空间
    initialState: {
        counter: 0,
        todoList: [],
        dataList: [], // 接口返回的数据
        locale: 'en',
        menuList: []
    }, // 初始值
    reducers: {
        counterIncrement: state => {
            // Redux Toolkit 允许我们在 reducers 写 "可变" 逻辑。它
            // 并不是真正的改变状态值,因为它使用了 Immer 库
            // 可以检测到“草稿状态“ 的变化并且基于这些变化生产全新的
            // 不可变的状态
            state.counter += 1;
        },
        counterDecrement: state => {
            state.counter -= 1;
        },
        setDataList(state, action) {
            state.dataList = action.payload;
        },
        setLocale(state, action) {
            state.locale = action.payload;
        },
        setMenuList(state, action) {
            state.menuList = action.payload;
        }
    },
     extraReducers: builder => {
	     for (const actionCase in extraReducers) {
            if (extraReducers.hasOwnProperty(actionCase)) {
               builder.addCase(actionCase, (state, action) =>
                   extraReducers[actionCase](state, action)
            	);
         	}
    	}
    }
});

export const actions = todosSlice.actions;

// reducer
export default todosSlice.reducer;
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';

// 模拟接口获取数据
const getDataService  = (params) => {
    console.log(params);

    return new Promise((resolve, reject) => {
        const res= {
            data: [
                {
                    id: 1,
                    name: 'zhangsan',
                    age: 20
                },
                {
                    id: 2,
                    name: 'lisi',
                    age: 30
                }
            ]
        };

        setTimeout(() => {
            resolve(res);
        }, 3000)
    });
};

// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {
    // 通过 payload 可以接收数据
    const { params } = payload;

    // 通过 getState 可以获取 store
    const { counter } = getState().page2;
    console.log(counter);

    // 通过 dispatch 可以分发 action
    // 在 createAsyncThunk 可以 dispatch 普通的 action
    dispatch(actions.counterIncrement());
    // 在 createAsyncThunk 也可以 dispatch 异步 action
    dispatch(asyncSetLocale({ locale: 'pl' }));

    const response = await getDataService(params);

    // 在这里 return 的数据,需要在 createSlice 的 extraReducers 里进行处理,更新 state
    return {
        dataList: response.data
    };
});

export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {
        return {
            locale: payload.locale,
            menuList: [{
                id: 11,
                menuName: '电站'
            }]
        };
    });


// 导出异步 actions
const asyncActions = {
    asyncGetData,
    asyncSetLocale
};

export default asyncActions;
简化 createSlice

由于需要给每个 state 定义至少一个修改 state 的 reducer,为了简化这一步,其实每个 state 只需要定义一个 setState 方法就可以了,处理数据可以放在外部去处理,我们只要传递 newState 即可。

比如定一个 name,修改它直接通过 dispatch(actions.setName('zhangsan'))

这样就可以封装一个统一的方法,来帮我们自动生成 state 对应的 setState,就不需要每个模块去单独写一遍,提升开发效率。

// src/store/createReducerSlice.js
// 工具方法
import { createSlice, createAction } from '@reduxjs/toolkit';

// 为每个 state 创建一个对应的 setState 方法,
// 如果 state 是 name,那么修改 name 则通过 setName 进行修改
// dispatch(simpleActions.setName('zhangsan'))
const createSetState = initialState => {
        const reducers = {};

        for (const key in initialState) {
            if (initialState.hasOwnProperty(key)) {
                const keyName = 'set' + key.substring(0, 1).toUpperCase() + key.substring(1);

                reducers[keyName] = (state, { payload }) => {
                    state[key] = payload;
                };
            }
        }

        return reducers;
    },
    createReducerSlice = ({
        pageIdentify, // 页面 id
        initialState = {}, // 定义 state 初始值
         reducers = {}, // 可选项,自定义 reducer。其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义
        extraReducers = {} // 可选项,如果 createAsyncThunk 中 return 了需要修改的 state,那么需要传递 extraReducers,统一修改 state;
    }) => {
        const updateState = createAction('updateState'),
            reducerSlice = createSlice({
                name: pageIdentify,
                initialState: initialState,
                // 简单reducer
                reducers: {
                	// 简单 reducer: 一次只能修改一个状态
                    ...createSetState(initialState),
                    // 其他更复杂的操作,比如对数组的添加/删除,如果不想在外部处理,那就在这里定义
                    ...reducers
                },
                extraReducers: builder => {
                    // 复杂 reducer 一次修改多个 state
                    builder.addCase(updateState, (state, { payload }) => {
                        for (const stateKey in payload) {
                            if (payload.hasOwnProperty(stateKey)) {
                                state[stateKey] = payload[stateKey];
                            }
                        }
                    });

                    for (const actionCase in extraReducers) {
                        if (extraReducers.hasOwnProperty(actionCase)) {
                            builder.addCase(actionCase, (state, action) =>
                                extraReducers[actionCase](state, action)
                            );
                        }
                    }
                }
            });

        // 自定义 caseReducer:通过 dispatch(caseReducer.updateState({ name: 'zhangsan', age: 20 })) 可一次修改多个 state
        reducerSlice.caseReducer = {
            updateState
        };

        return reducerSlice;
    };

export default createReducerSlice;
// src/pages/page2/asyncActions.js
import {  createAsyncThunk } from '@reduxjs/toolkit';
import { actions } from './reducer';

// 模拟接口获取数据
const getDataService  = (params) => {
    console.log(params);

    return new Promise((resolve, reject) => {
        const res= {
            data: [
                {
                    id: 1,
                    name: 'zhangsan',
                    age: 20
                },
                {
                    id: 2,
                    name: 'lisi',
                    age: 30
                }
            ]
        };

        setTimeout(() => {
            resolve(res);
        }, 3000)
    });
};

// 定义异步 action
export const asyncGetData = createAsyncThunk('page2/fetchPosts', async (payload, { dispatch, getState }) => {
    // 通过 payload 可以接收数据
    const { params } = payload;

    dispatch(asyncSetLocale({ locale: 'pl' }));

    const response = await getDataService(params);

    dispatch(actions.setDataList(response.data));
});

export const asyncSetLocale = createAsyncThunk('page2/setLocael', async (payload, { dispatch, getState }) => {
        dispatch(actions.setLocale(payload.locale));
        dispatch(actions.setMenuList([{ id: 11, menuName: '电站' }]));
    });


// 导出异步 actions
const asyncActions = {
    asyncGetData,
    asyncSetLocale
};

export default asyncActions;
// src/pages/page2/reducer.js
// import asyncActions from './asyncAtions';
// import { createExtraReducers } from '@store/createExtraReducers.js';
import createReducerSlice  from '@store/createReducerSlice.js';

// const extraReducers = createExtraReducers(asyncActions);

// 使用二次封装的 createReducerSlice
const reducerSlice = createReducerSlice({
    pageIdentify: 'page2',
    initialState: {
        counter: 0,
        todoList: [],
        dataList: [], // 接口返回的数据
        locale: 'en',
        menuList: []
    }, // 初始值
    // extraReducers
});

// 普通 actions
export const actions = reducerSlice.actions;
export const caseReducer = reducerSlice.caseReducer;

// reducer
export default reducerSlice.reducer;

配置 react-router

安装 react-router-dom

yarn add react-router-dom
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { HashRouter, Navigate, Route, Routes } from 'react-router-dom';
import { store } from './store/index';
import Index from '@pages/index.jsx';
import Login from "./pages/login";
import Register from "./pages/register";
import Home from "./pages/home";
import Station from "./pages/station";
import Device from "./pages/device";

const App = () => {
    return <HashRouter>
        <Provider store={store}>
            <Routes>
                <Route path="/login" element={<Login />} />
                <Route path="/register" element={<Register />} />
                {/* 这里是嵌套路由 */}
                <Route path="/" element={<Index />}>
                	{/* } /> 是为了告诉路由 访问 / 的时候跳转哪个组件 */}
                    <Route path="/" element={<Navigate to="/home" />} />
                    <Route path="/home" element={<Home />} />
                    <Route path="/station" element={<Station />} />
                    <Route path="/device" element={<Device />} />
                </Route>
            </Routes>
        </Provider>
    </HashRouter>;
};

export default App;
import React from 'react';
import Header from './components/header';
import Footer from './components/Footer';
import { Outlet } from "react-router-dom";

const Index = () => {
    return (
        <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
            <Header />

           <div style={{ width: '100%', flex: 1 }}>
               {/*  子路由占位  */}
               <Outlet />
           </div>

            <Footer />
        </div>
    );
};

export default React.memo(Index);

你可能感兴趣的:(react.js,前端,前端框架)