REACT-redux和数据持久化react-redux
1. redux介绍
1.1 描述
Redux最主要是用作应用状态的管理。简言之,Redux用一个单独的常量状态树(state对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用actions和reducers),这样就可以进行数据追踪。
1.2 使用的三大原则
- state以单一对象存储在store对象中
- state只读(每次都会返回一个新的对象)
- 使用纯函数reducer执行state更新
1.3 官方文档
2. 安装redux相关包
npm i redux
// 以下两个是redux中间件,用来处理异步的action creator
npm i redux-thunk
npm i redux-promise
3. 创建目录关系
src下创建文件夹redux
3.1 store.js
import {createStore,combineReducers, applyMiddleware,compose} from 'redux';
import TabbarReducer from './reducers/TabbarReducer';
import CityReducer from './reducers/CityReducer';
import CinemaListReducer from './reducers/CinemaListReducer';
//redux-thunk、redux-promises是redux中间件,用来处理异步的action creator
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'
//combineReducers合并reducer
const reducer = combineReducers({
TabbarReducer,
CityReducer,
CinemaListReducer
});
//compose-redux浏览器调试工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,composeEnhancers(applyMiddleware(reduxThunk,reduxPromise)));
export default store;
3.1.1 creareStore原理
function createStore(reducer){
let list = [];
let state = reducer(undefined,{});//传参undefined函数也会使用默认值
function subscribe(callback){
list.push(callback);
}
function dispatch(action){
state = reducer(state,action);
list.forEach(cb=>{
cb && cb()
});
}
function getState(){
return state
}
return {
subscribe,
dispatch,
getState
}
}
3.2 reducers内文件
TabbarReducer.js
// 定义一个状态
let initialState = {
show: true
};
// 利用reducer将store和action串联起来
function reducer(prevState = initialState, action) {
let newState = {...prevState};
switch (action.type) {
case 'hidden-tabbar':
newState.show = action.show;
return newState;
case 'show-tabbar':
newState.show = action.show;
return newState;
default:
return prevState;
}
}
export default reducer;
3.3 actionCreator
3.3.1 同步-TabbarAction.js
function show(){
return {
type:'show-tabbar',
show:true
}
}
function hidden(){
return {
type:'hidden-tabbar',
show:false
}
}
export {show,hidden}
3.3.2 异步-CinemaListAction.js
import axios from 'axios'
//redux-thunk
function getCinemaList(){
return (dispatch) =>{
axios({
url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
method:'get',
headers:{
'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list'
}
}).then(res=>{
dispatch({
type:'change-cinemaList',
cinemaList:res.data.data.cinemas
})
})
}
}
//redux-promise
// function getCinemaList(){
// return axios({
// url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
// method:'get',
// headers:{
// 'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
// 'X-Host': 'mall.film-ticket.cinema.list'
// }
// }).then(res=>{
// return {
// type:'change-cinemaList',
// cinemaList:res.data.data.cinemas
// }
// })
// }
//ES7 async await 依次执行异步,全部执行完,再执行最后的代码
// async function getCinemaList(){
// let action = await axios({
// url:"https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=7406159",
// method:'get',
// headers:{
// 'X-Client-Info': '{"a":"3000","ch":"1002","v":"5.0.4","e":"16395416565231270166529","bc":"110100"}',
// 'X-Host': 'mall.film-ticket.cinema.list'
// }
// }).then(res=>{
// return {
// type:'change-cinemaList',
// cinemaList:res.data.data.cinemas
// }
// })
// return action
// }
export default getCinemaList;
4. store api应用
- subscribe-订阅
- dispatch-事件分发
- getstate-获取状态
注意:不管是任意的的一个dispatch都会调用应用中的全部订阅,所以在组件销毁的时候要取消订阅,不过在使用react-redux就不用担心,react-redux会帮忙处理好订阅和取消订阅。
4.1 class组件-Cinemas.js
import React, { Component } from 'react'
import store from '../redux/store'
import getCinemaList from '../redux/actionCreator/CinemaListAction'
export default class Cinemas extends Component {
state = {
cityName:store.getState().CityReducer.cityName,
cinemaList:store.getState().CinemaListReducer.cinemaList
};
componentDidMount(){
this.unsubscribe = store.subscribe(()=>{
// console.log('cinemas-订阅',this.state.cinemaList)
this.setState({cinemaList:store.getState().CinemaListReducer.cinemaList})
});
if(store.getState().CinemaListReducer.cinemaList.length === 0){
store.dispatch(getCinemaList());
};
}
componentWillUnmount(){
//取消订阅
this.unsubscribe();
}
render() {
return (
<div>
<div style={{overflow:'hidden'}}>
<div style={{float:'left'}} onClick={()=>{
this.props.history.push('/city');
}}>
{this.state.cityName}
</div>
<div style={{float:'right'}} onClick={()=>{
this.props.history.push('/cinemas/search');
}}>
搜索
</div>
</div>
{this.state.cinemaList.map((item) => {
return (
<dl key={item.cinemaId} style={{padding:'10px'}}>
<dt>{item.name}</dt>
<dd style={{fontSize:'12px',color:'gray'}}>{item.address}</dd>
</dl>
)
})}
</div>
)
}
}
4.2 函数组件-Search.js
import React,{useState,useEffect,useMemo}from 'react'
import store from '../redux/store'
import getCinemaList from '../redux/actionCreator/CinemaListAction'
export default function Search() {
const [cinemaList,setCinemaList] = useState(store.getState().CinemaListReducer.cinemaList);
const [text,setText] = useState('');
useEffect(()=>{
let unsubscribe = store.subscribe(()=>{
setCinemaList(store.getState().CinemaListReducer.cinemaList)
});
if(store.getState().CinemaListReducer.cinemaList.length === 0){
store.dispatch(getCinemaList());
};
return ()=>{
//取消订阅
unsubscribe();
}
},[])
const getList = useMemo(() => cinemaList.filter(item =>{
return item.name.toUpperCase().includes(text.toUpperCase()) || item.name.toLowerCase().includes(text.toLowerCase())
}),[cinemaList,text]);
return (
<div>
<input value={text} onChange={(evt)=>{
setText(evt.target.value)
}}></input>
<ul>
{getList.map((item) => {
return (
<dl key={item.cinemaId} style={{padding:'10px'}}>
<dt>{item.name}</dt>
<dd style={{fontSize:'12px',color:'gray'}}>{item.address}</dd>
</dl>
)
})}
</ul>
</div>
)
}
5. 调试工具redux-devtools-extension
5.1 安装redux-devtools-extension
5.1.1下载chrome可用版本
5.1.2 安装到chrome浏览器中
浏览器右上角三点=>更多工具=>扩展程序=>开发者模式
将安装包拖入
5.2 store.js中进行配置
引入compose
配置内容在上述的store.js中
5.3 使用redux-devtools-extension
如果不能使用,重启浏览器
6. 数据持久化react-redux
6.1 安装相关包
npm i react-redux
npm i redux-persist //数据持久化包
6.2 store.js改写
import {createStore,combineReducers, applyMiddleware,compose} from 'redux';
import TabbarReducer from './reducers/TabbarReducer';
import CityReducer from './reducers/CityReducer';
import CinemaListReducer from './reducers/CinemaListReducer';
//redux-thunk、redux-promises是redux中间件,用来处理异步的action creator
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'
//数据持久化
import {persistStore,persistReducer} from 'redux-persist'
import storage from 'redux-persist/lib/storage';
//数据持久化配置项
const persistConfig = {
key:'redux',
storage:storage,
//localStorage: import storage from 'redux-persist/lib/storage'
//sessionStorage: import storageSession from 'redux-persist/lib/storage/session'
whitelist:['CityReducer'],//白名单只保存CityReducer
// blacklist:['CityReducer'],//黑名单仅不保存CityReducer
}
//combineReducers合并reducer
const reducer = combineReducers({
TabbarReducer,
CityReducer,
CinemaListReducer
});
//被数据持久化的reducer
const persistedReducer = persistReducer(persistConfig,reducer);
//compose-redux浏览器调试工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
persistedReducer,
composeEnhancers(applyMiddleware(reduxThunk,reduxPromise))
);
//被数据持久化的store
const persistor = persistStore(store);
export {store,persistor};
6.3 根组件改写
- React-Redux 提供Provider组件,可以让容器组件拿到state。
- Provider组件,可以让容器组件拿到state , 使用了context
import React from "react";
import ReactDOM from "react-dom/client";
// import ReactDOM from "react-dom";
import App from './06-react-redux/App'
import {store,persistor} from './06-react-redux/redux/store'
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
// ReactDOM.render(
// // <React.StrictMode>
// <Provider store={store}>
// <PersistGate loading={null} persistor={persistor}>
// <App />
// </PersistGate>
// </Provider>
// // </React.StrictMode>
// ,document.getElementById('root'))
//v18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
// </React.StrictMode>
);
6.4 改写使用subscribe和dispatch
- React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这
两种组件连起来。 - connect 是HOC(Higher-order component), 高阶组件
6.4.1 UI组件 和 容器组件
UI组件 和 容器组件
(1) UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
(2) 容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 AP
6.4.2 应用到组件
6.4.2.1 Cinemas.js
connect(将来传给孩子的属性,将来给孩子传的回调函数)(原组件)
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import getCinemaList from '../redux/actionCreator/CinemaListAction'
function Cinemas(props) {
let {cinemaList,getCinemaList,cityName} = props;
useEffect(()=>{
if(cinemaList.length === 0){
getCinemaList();
};
},[cinemaList,getCinemaList]);
return (
<div>
<div style={{overflow:'hidden'}}>
<div style={{float:'left'}} onClick={()=>{
props.history.push('/city');
}}>
{cityName}
</div>
<div style={{float:'right'}} onClick={()=>{
props.history.push('/cinemas/search');
}}>
搜索
</div>
</div>
{cinemaList.map((item) => {
return (
<dl key={item.cinemaId} style={{padding:'10px'}}>
<dt>{item.name}</dt>
<dd style={{fontSize:'12px',color:'gray'}}>{item.address}</dd>
</dl>
)
})}
</div>
)
}
//connect(将来传给孩子的属性,将来给孩子传的回调函数)(原组件)
const mapStateToProps = (state) => {
return {
cityName:state.CityReducer.cityName,
cinemaList:state.CinemaListReducer.cinemaList
}
};
const mapDispatchToProps ={
getCinemaList
};
export default connect(mapStateToProps,mapDispatchToProps)(Cinemas);
6.4.2.2 City.js
connect(将来传给孩子的属性,将来给孩子传的回调函数)(原组件)
import React,{useState} from 'react'
import { connect } from 'react-redux'
function City(props) {
const [cityList] = useState(['广东','深圳','杭州','上海']);
return (
<div>
<ul>
{
cityList.map(item=>{
return <li key={item} onClick={()=>{
props.changeCity(item);
// props.history.push('/cinemas')
props.history.goBack();
}}>
{item}
</li>
})
}
</ul>
</div>
)
}
const mapDispatchToProps ={
changeCity(item){
return {
type:'change-city',
cityName:item
}
}
};
export default connect(null,mapDispatchToProps)(City);
6.5 类似connect的高阶组件原理
import React, { Component } from 'react'
class NotFound extends Component {
componentDidMount(){
console.log(this.props)
}
render() {
return (
<div>
404 NotFound
</div>
)
}
}
function myconnect(cb,obj){
if(cb){
var state = cb();
};
return (MyComponent)=>{//return 一个处理组件的函数
return (props)=>{//return 一个函数组件
return (//return 函数组件中返回具体的组件内容
<div style={{color:'red'}}>
{/* 传递route中的props.history等属性、state和obj */}
<MyComponent {...props} {...state} {...obj}/>
</div>
)
}
}
}
const mapStateToProps = (state) => {
return {
a:1,
b:2
}
};
const mapDispatchToProps ={
show(){
},
hide(){
}
};
export default myconnect(mapStateToProps,mapDispatchToProps)(NotFound);