初始配置
pinia:
1.通过create-vue
脚手架在命令行选择pinia
模板生成
2.在项目后续添加使用npm add pinia
也可以添加并追加模板
redux:
1.使用create-react-app
脚手架带的pinia
模板通过npx create-react-app my-app --template redux-typescript
生成
2.在项目后续添加使用npm add redux
也可以添加但不能追加模板,需要自己配置
开发工具
pinia:
集成在vue-devtool
中redux:
与react-devtool
分离,需要独立下载redux-devtool
初始化总仓库
pinia:
创建一个store文件夹>每个仓库扁平化存储(没有入口文件,现拿现取,直接在引入对应仓库)
redux:
1.创建一个store文件夹>index.ts入口文件,引入所有仓库的导出的Reducer,统一管理所有的仓库
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({reducer: {//每次创建一个新仓库必须导出各自reducer并在此导入(可以利用构建工具批量导入)counter: counterReducer,},
});
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
2.所有仓库另外放在festures存储
初始化一个仓库
pinia:
import { defineStore } from "pinia";
export const useCountStore = defineStore({
id: "countStore",
state: () => ({
count:0
}),
actions: {
increment(number) {
this.count+=number
}
},
});
redux:
import { createSlice } from '@reduxjs/toolkit'
const demoStore = createSlice({//对标idname: 'demo',//对标初始化stateinitialState: { count: 0 },//对标actionreducers: {//vuex有点类似于redux的写法 increment(state, action) {state.count += action.payload} },
})
//导出actions
export const { increment } = demoStore.actions
//导出reducer给入口文件注册仓库
export default demoStore.reducer
获得仓库中的值
pinia:
直接通过模块化的方式获取仓库
//引入仓库
import { useCountStore } from "@/stores/count";
//解构仓库必须要用storeToRefs转化为ref不然会失去响应式
import { storeToRefs } from "pinia";
const countStore=useCountStore()
const {count}=storeToRefs(countStore)
console.log(count.value)
//也可以不解构
console.log(countStore.count)
redux:
利用redux的useSelector这个api
import { useSelector } from 'react-redux'
function App() {
//可以通过直接解构简单化(useSelector是响应式,返回的值如果发生变化会重新渲染)const demoState = useSelector(store => store.demo)//调用getState返回当前时刻state的值,如果后续发生变化不会重新渲染const demoState2=store.getState()return (<div className="App">{demoState.count}</div>)
}
export default App
修改仓库中的值
pinia:
直接通过模块化的方式获取仓库
//引入仓库
import { useCountStore } from "@/stores/count";
const countStore=useCountStore()
//函数解构跟响应式无关,可以直接解构
const {increment}=countStore
//也可以不解构
countStore.increment(参数)
redux:
通过useDispatch这个api触发仓库下的actions
import { useDispatch } from 'react-redux'
//获取仓库导出的actions
import { increment } from './features/demo/demo'
function App() {const dispatch = useDispatch()return (<div className="App">{count}<buttononClick={() => {dispatch(increment(参数))//在这里调用}}>按钮</button></div>)
}
异步修改仓库中的值
pinia:
1.直接在action使用异步,与同步写法无异
import { defineStore } from "pinia";
export const useCounterStore = defineStore({
id: "counter",
state: () => ({
counter: 0
}),
actions: { async incrementAsync(num: number) {
await new Promise<void>(rej => {
setTimeout(() => {
this.counter += num;
rej();
}, 2000);
});}
}
});
在Vue-Devtool调试工具显示具体信息
redux:
1. 通过redux-toolkit的createAsyncThunk创建异步任务,通过extraReducers处理不同状态的回调,目的是解耦异步任务和状态处理,让其专注于各自的任务
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5defb5b6541b44e0adaf87ae2a7b0354~tplv-k3u1fbpfcp-watermark.image?" style="margin: auto" />
const demoStore = createSlice({name: 'demo',initialState: { count: 0 },reducers: {},//用于处理异步不同状态extraReducers(builder) {builder.addCase(incrementAsync.pending, state => {//Promise.pending状态时执行console.log('???? ~ 进行中!')}).addCase(incrementAsync.fulfilled, (state, { payload }) => {//Promise.fulfiled状态时执行,真正修改state的逻辑放此处console.log('???? ~ fulfilled', payload)state.count += payload}).addCase(incrementAsync.rejected, (state, err) => {//Promise.rejected状态时执行console.log('???? ~ rejected', err)})},
})
//导出异步方法,第一个参数是命名空间,也是在redux-devtool显示的name,第二个参数是dispatch传入的参数
export const incrementAsync = createAsyncThunk('demo/incrementAsync', async (num: number) => {return await new Promise<number>((resolve, reject) => {incrementAsync(() => {resolve(num)}, 2000)})
})
export default demoStore.reducer
2. 通过dispatch调用
import { useDispatch } from 'react-redux'
import { AppDispatch } from './app/store'
//引入异步任务
import { incrementAsync } from './features/demo/demo'
function App() {
//必须要添加AppDispach类型(从入口仓库中导出),不然默认执行同步任务,ts会报错const dispatch: AppDispatch = useDispatch()return (<div className="App"><buttononClick={() => {//传入参数并调用dispatch(incrementAsync(2))}}>按钮</button></div>)
}
export default App
3. Redux-Devtool调试工具显示具体信息
getter(计算属性)
pinia:
利用computed存储在getter中,和取state一样,但不能改**redux:
没有getter这个单独存储计算属性的容器,只能在useAppSelector取出时返回一个计算属性**
const count = useSelector((state) => state.counter.value + state.counter.status);
持久化
pinia:
1.安装pinia-plugin-persist:npm i pinia-plugin-persist
2.在main.ts引入pinia和持久化插件并注册
import piniaPluginPersist from "pinia-plugin-persist";
const pinia = createPinia();
pinia.use(piniaPluginPersist);
app.use(pinia);
3.在需要持久化的仓库追加persist属性
import { defineStore } from "pinia";
export const useUserinfoStore = defineStore({
id: "userinfo",
state: () => ({}),
actions: {},
persist: {
//持久化插件的开启状态
enabled: true,
strategies: [
{//此仓库中要持久化的属性
paths: ["xxx"],
storage: localStorage
// 指定要持久化的数据,默认所有 state 都会进行缓存,你可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
}
]
}
});
redux:
1.安装redux-persist:npm i redux-persist -S
,在redux-toolkit使用redux-persist
2.配置仓库入口文件
import { configureStore, ThunkAction, Action, combineReducers } from '@reduxjs/toolkit'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import counterReducer from '../features/counter/counterSlice'
//这是原先放在configureStore中的reducer的各个仓库
//使用combineReducers合并然后再后续封装一层persistReducer再放入configureStore实现持久化
const reducers = combineReducers({counter: counterReducer
})
//这是持久化的配置
const persistConfig = {key: 'root',storage,
//持久化白名单(以combineReducers的属性名为标准,只能精确到仓库,不能像pinia一样精确到仓库的属性)whiteList:['counter']
}
//封装持久化
const persistedReducer = persistReducer(persistConfig, reducers)
//此时的reducer就是持久化后的配置
export const store = configureStore({reducer: persistedReducer
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType,RootState,unknown,Action<string>
>
3.在index.tsc启用持久化并且添加PersistGate
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
import reportWebVitals from './reportWebVitals'
import './index.css'
import { PersistGate } from 'redux-persist/integration/react'
import persistStore from 'redux-persist/es/persistStore'
//利用persistStore就已经开启了持久化
const persistor = persistStore(store)
const container = document.getElementById('root')!
const root = createRoot(container)
root.render(<React.StrictMode><Provider store={store}>//这里继续封装一层persistGate是为了在持久化还未完成的时候提供loading过渡动画<PersistGate loading={null} persistor={persistor}><App /></PersistGate></Provider></React.StrictMode>
)
reportWebVitals()
4.关于PersistGate的解释如果不加PersistGate的组件也是没有问题的,持久化已经在你创建persistStore(store)
的时候已经开启了,这个PersistGate是为了在持久化未完成的时候添加一个你在loading配置的过渡动画组件_官方解释:_
If you are using react, wrap your root component with PersistGate. This delays the rendering of your app’s UI until your persisted state has been retrieved and saved to redux. NOTE the loading prop can be null, or any react instance, e.g.
PersistGate``loading={<Loading />}
监听器subscribe
pinia:
1.pinia自带$subscribe,可以注册为一个插件全局运行或者在仓库中运行,可以订阅一个仓库或多个仓库
useHistoryStore().$subscribe((mutation, state) => {
//mutation包含了本次改变的具体信息,state包含这个仓库的最新值
});
2.也可以使用vue的watch,wateffect进行监听(个人更推荐,比$subscribe更灵活)
redux:
1.redux没有自带监听,但是可以用生态下的redux-watch实现订阅:安装npm i redux-watch -S
2.在store入口文件初始化后进行监听
import watch from 'redux-watch'
//这里不像持久化一样只能精确到一个仓库,可以通过.拼接的方式精确到仓库的属性
let w = watch(store.getState, 'counter.count')
store.subscribe(
//newVal, oldVal, objectPath分别代表新值,旧值与发生变化的属性w((newVal, oldVal, objectPath) => {console.log('%s changed from %s to %s', objectPath, oldVal, newVal)})
)
3.打印输出要注意的是: 如果同时使用了持久化插件,那么在刷新页面时数据拉取一次本地存储并赋值,同时也会触发一次订阅事件
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享