react框架研习
一、react项目技术架构组成
- 基础技术react版本18.2.0
- 路由插件react-router
-
数据仓库react-redux
- UI组件库antd
- dotenv-cli来区分不同环境变量
二、框架搭建
脚手架创建基础项目
create-react-app my-react-app
刚创建的基础项目是没有集成路由、中央数据仓库、样式组件库。
集成路由
安装路由依赖
yarn add react-router-dom
创建路由文件,在根目录下创建router目录,router目录下创建index.js路由文件,内容如下,具体页面按自己实际需求
import React from "react"; import { Navigate } from "react-router-dom"; const Home = React.lazy(() => import("../views/home")); const Page1 = React.lazy(() => import("../views/page1")); const Page2 = React.lazy(() => import("../views/page2")); const NotFound = React.lazy(() => import("../views/notFound")); export default [ { path: "/", element: <Navigate to="/home" />, }, { path: "/home", element: <Home />, }, { path: "/page1", element: <Page1 />, }, { path: "/page2", element: <Page2 />, }, { path: "*", element: <NotFound />, }, ];
在react入口文件src下index.js里用路由标签包裹根节点
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { HashRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Suspense fallback={<Spin size="large" style={{ marginTop: 100 }} />}>
<HashRouter>
<App />
</HashRouter>
</Suspense>
);
react-router有两种路由模式,history和hash,HashRouter对应hash路由,BrowserRouter对应history路由,我这里用的是hash路由,具体按实际情况选择
在App.js如下,类似vue的路由视图,这样路由跳转的时候,页面就会在页眉和页脚之间渲染,这样路由就集成好了。
import { useRoutes } from "react-router-dom";
import routes from "./router";
function App() {
return (
<div className="App">
<div className="header">页眉</div>
<div className="content">{useRoutes(routes)}</div>
<div className="footer">页脚</div>
</div>
);
}
集成redux,一般使用redux都会使用工具类,我这里使用了reduxjs/toolkit,安装依赖
yarn add react-redux
yarn add @reduxjs/toolkit
创建中央数据仓库,在src下新增store目录,在store目录下新建index.js,这是redux主入口文件,内容如下
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import storeA from "./modules/storeA";
import storeB from "./modules/storeB";
//合并reducer
const rootReducer = combineReducers({
storeA,
storeB,
});
//创建store对象
const store = configureStore({
reducer: {
storeA,
storeB
}
});
export { store};
redux可以包含多模块,这里包含了两个,storeA和storeB,下面是storeA的内容,storeB差不多就不写出来了,用redux工具类toolkit创建的切片对象,包含name,initialState,reducers,这里工具类会默认创建同名的action,就不需要再手动创建,只需要export导出就行,调用action时会直接调用同名reducer,但是reducer只能是同步,如果有异步的需求只能创建异步action来实现,下面写了两种方式,一种是使用reduxjs/toolkit,它默认集成了reduxThunk,可以直接使用方法createAsyncThunk来创建异步action,或者直接用两层箭头函数,也能实现异步action的效果。
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { getNewsData } from "../../api";
export const counterSlice = createSlice({
name: "storeA",
initialState: {
value: 0,
obj: {
num: 0,
},
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
setObjAttr: (state, action) => {
state.obj.num += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount, setObjAttr } =
counterSlice.actions;
//action creator
export const axiosFun = createAsyncThunk(
"storeA/axiosFun",
async (arg, { dispatch }) => {
const result = await getNewsData({
currentPage: 1,
pageSize: 20,
category: "30571001",
});
if (result.type === "00") {
dispatch(setObjAttr(result.total));
}
}
);
export const axiosFun2 = (arg) => async (dispatch) => {
const result = await getNewsData({
currentPage: 1,
pageSize: 20,
category: "30571001",
});
if (result.type === "00") {
dispatch(setObjAttr(result.total));
}
};
export default counterSlice.reducer;
页面调用方式有两种,一种是直接import引入,使用dispatch调用,还有一种是直接使用dispatch使用type参数调用,如下
<Button onClick={() => dispatch(increment())}>+1</Button>
<Button onClick={() => dispatch({ type: "storeA/increment" })}>+1</Button>
对于异步操作还可以使用中间件saga,首先创建saga文件
import { takeLatest, put, all } from "redux-saga/effects";
const effects = {
testSaga1: function* (e) {
console.log("通过saga1中间件调用了effect");
yield put({ type: "storeA/increment" });
},
testSaga2: function* (e) {
console.log("通过saga2中间件调用了effect");
yield put({ type: "storeA/increment" });
},
};
function* watcher() {
yield all([
yield takeLatest("storeA/testSaga1", effects.testSaga1),
yield takeLatest("storeA/testSaga2", effects.testSaga2),
]);
}
export default watcher;
takeLates:表示监听最新的一次调用,第一个参数表示监听的类型,比如多次dispatch({type:"storeA/testSaga1"}),saga只会触发一次,如果使用的是takeEvery,则有几次调用就会触发几次,上诉例子表明监听两个调用类型,当监听到调用后回去执行effects里面的方法,effects里面的方法使用的是put,类型dispatch。
上面只是创建了saga的中间件文件,需要把saga中间件注册到redux中,如下,修改原本创建store方法,把sagaMiddleware中间件添加进去。
import createSagaMiddleware from "redux-saga";
import saga from "./saga"; //这个就是上面刚刚创建的saga文件
//创建saga中间件
const sagaMiddleware = createSagaMiddleware();
//创建store对象
const store = configureStore({
reducer: {
storeA,
storeB
},
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware({
serializableCheck: false,
}).concat(sagaMiddleware);
},
});
sagaMiddleware.run(saga);
下面就是调用方式。
<Button
onClick={() => {
dispatch({
type: "storeA/testSaga1",
payload: { name: "zhouyun", sex: "man" },
});
}}
>
调用saga1
</Button>
到这里基本框架搭建完毕,但是缺少redux的持久化支持,因为redux没做持久化支持刷新页面会出现保存在redux的数据会丢失,基本的解决思路是使用localStorage来存储redux的数据,但localStorage是没有过期时间的,所以需要有个数据过期功能。所以这里我使用了第三方的解决方案redux-persist,本质也是使用的localStorage来存储数据,这里有两个概念,分为持久化和水化,持久化就是redux-persist将redux数据保存到localStorage,并且会打上时间戳,水化就是将localStorage的数据读取出来返回给redux的过程,数据过期的逻辑就在这两个过程里,持久化过程打时间戳,水化过程判断时间戳,如果已经过期则重置数据使存储的数据失效。下面是具体集成步骤:
修改原有的store文件,下面这个是最全的文件,包含了saga和redux-persist持久化,持久化配置里面的whitelist白名单就是表示需要使用持久化方案的store模块,blacklist黑名单表示不使用持久化方案的store模块,未使用持久化方案的数据池在页面刷新后就会丢失数据,使用了持久化方案的数据池在每次页面操作都会刷新时间戳,使时间戳一直保持在最新时间,长时间未操作页面的时间超过设置的过期时间后就会失效。
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import storeA from "./modules/storeA";
import storeB from "./modules/storeB";
import createSagaMiddleware from "redux-saga";
import saga from "./saga";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import expireReduxState from "./persistExpire";
//合并reducer
const rootReducer = combineReducers({
storeA,
storeB,
});
//持久化配置
const persistConfig = {
key: "root",
storage,
whitelist: ["storeA"],
blacklist: ["storeB"],
transforms: [
//自定义transform,定义持久化和水化逻辑
expireReduxState("storeA", {
expireAfter: 5, //持久化过期时间,单位秒
expiredState: {
//持久化过期后的重置初始化对象
value: 0,
obj: {
num: 20,
},
},
}),
],
};
//持久化reducer
const persistReducers = persistReducer(persistConfig, rootReducer);
//创建saga中间件
const sagaMiddleware = createSagaMiddleware();
//创建store对象
const store = configureStore({
reducer: persistReducers,
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware({
serializableCheck: false,
}).concat(sagaMiddleware);
},
});
sagaMiddleware.run(saga);
const persistor = persistStore(store);
export { store, persistor };
persistExpire.js是自定义的transform方法,自定义了持久化和水化的逻辑,下面这个就是文件内容:
import { createTransform } from "redux-persist";
//时间戳过期时间属性key
const EXPIRE_DEFAULT_KEY = "persistedTimestamp";
/**
* Returns the numeric value of the specified date as the number of seconds since January 1, 1970, 00:00:00 UTC
* @param {Date} date
*/
const getUnixTime = (date) => {
return Number((date.getTime() / 1000).toFixed(0));
};
/**
* Creates transform object with defined expiry config
* @param {string} key - reducerKey
* @param {object} config - expiry config
* @returns {Transform<{}, any>}
*/
const expireReduxState = (key, config) => {
const default_config = {
expiredState: {},
expireKey: EXPIRE_DEFAULT_KEY,
expireAfter: null, // expiration time in seconds
};
config = Object.assign({}, default_config, config);
return createTransform(
//持久化逻辑,添加过期时间
(inbound) => {
inbound = inbound || {};
if (config.expireAfter) {
inbound = Object.assign({}, inbound, {
[config.expireKey]: new Date(),
});
}
return inbound;
},
//水化逻辑,判断时间戳是否过期
(outbound) => {
outbound = outbound || {};
if (config.expireAfter && outbound.hasOwnProperty(config.expireKey)) {
let stateAge =
getUnixTime(new Date(outbound[config.expireKey])) +
config.expireAfter;
let current = getUnixTime(new Date());
// if persisted state expired, set it to default state.
if (stateAge < current) {
outbound = config.expiredState;
}
}
return outbound;
},
{
whitelist: [key],
}
);
};
export default expireReduxState;
到这一个基本的react技术架构就搭建完成了,至于react hooks的使用可以自己查看react官网的使用例子。
在react入口文件index.js添加redux的配置,下面是部分代码片段,包含了UI组件库antd,redux,持久化persistor,以及路由配置。
import { HashRouter, BrowserRouter } from "react-router-dom";
import { Spin, ConfigProvider } from "antd";
import { store, persistor } from "./store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import zhCN from "antd/locale/zh_CN";
root = ReactDOM.createRoot(mountNode);
root.render(
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Suspense fallback={<Spin size="large" style={{ marginTop: 100 }} />}>
<BrowserRouter>
<App />
</BrowserRouter>
</Suspense>
</PersistGate>
</Provider>
</ConfigProvider>
);

浙公网安备 33010602011771号