Bootstrap

redux + react-redux + redux-persist实现react项目的store,以及数据持久化

在这里插入图片描述
项目结构 create-react-app创建的

先说redux

defaultState.js
默认的state

export default {
  hasLogin: false, //是否登录的标记
  userInfo: null,
  memu: [1, 2, 3],// 用于页面权限,后端返回
  authKey: '',
  sessionId: '',
  contractList: [], // 合同类型
  defaultSelectedKeys: ['1'], // 左边导航栏默认选中,
}

action.js
返回一个对象,触发reducer

import $api from '@/api'

export const getContractList = () => {
  return async dispatch => { // 异步的action
    try {
      let res = await $api.post('/common/service_list', {
        action_id: '0c0a2b9cd16e9594774c2979951d709b4',
        pid: '79ff0e6ef55fc2e14cfdc4b81193de6f70'
      })
      if (res.code === 200) {
        dispatch({
          type: 'setContractList',
          data: res.data
        })
      }
    } catch (error) {
      console.error(error)
    }
  }
}

export const setUserInfo = (payload) => {
  return {
    type: "setUserInfo",
    data: payload
  }
}
// 设置左边导航默认值
export const setDefaultSelectedKeys = (val) => {
  return {
    type: "setDefaultSelectedKeys",
    data: val
  }
}

reducer.js
处理action返回新的state

import defaultState from './defaultState'

export const initData = (state = defaultState, action) => {
  switch (action.type) {
    case 'setContractList':
      return { ...state, contractList: action.data };
    case 'setUserInfo':
      let { userInfo, memu, authKey, sessionId } = action.data
      return { ...state, userInfo, memu, authKey, sessionId, hasLogin: true }
    case 'setDefaultSelectedKeys':
      return { ...state, defaultSelectedKeys: action.data };
    default: return state;
  }
}

store的构建
用到两个插件 一个是用于在action里面支持异步的,一个是持久化数据的(利用的localstorage/sessionSt0rage),类似于vue的 vuex-persistedstate

import { createStore, combineReducers, applyMiddleware } from 'redux';
import * as initData from './reducer';
import thunk from 'redux-thunk'; // 用来在action里面支持异步
import { persistStore, persistReducer } from 'redux-persist' // 用来避免刷新导致store重置
import storage from 'redux-persist/lib/storage';
let rootReducer = combineReducers({ ...initData })
const myReducer = persistReducer({
  key: 'root',
  storage
}, rootReducer);
let store = createStore(
  myReducer,
  applyMiddleware(thunk)
);
export const persistor = persistStore(store);
export default store;

下面就是把store挂载在react的组件上了,
主要利用react-redux插件

项目的主入口

它提供一个核心组件Provider (把store加载到react的DOM中)
和一个核心方法 connect (把state 和 action映射到组件的props)

import React from 'react';
import ReactDOM from 'react-dom';
import Route from './router/';
import * as serviceWorker from './serviceWorker';
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';
import store, { persistor } from '@/store/store';
import 'antd/dist/antd.css'
import '@/iconfont/iconfont.css'
import { PersistGate } from 'redux-persist/integration/react';
import zhCN from 'antd/es/locale-provider/zh_CN';
import { LocaleProvider } from 'antd'// antd格式化为中文,默认英文?鄙视!
const render = Component => {
  ReactDOM.render(
    //绑定redux、热加载
    <Provider store={store}>
      <AppContainer>
        <PersistGate loading={null} persistor={persistor}>
          <LocaleProvider locale={zhCN}>
            <Component />
          </LocaleProvider>
        </PersistGate>
      </AppContainer>
    </Provider>,
    document.getElementById('root'),
  )
}

render(Route);

// Webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./router/', () => {
    render(Route);
  })
}
serviceWorker.unregister();

页面上的应用实例

import React, { Component } from 'react';
import { connect } from 'react-redux';
// import PropTypes from 'prop-types'; // 这个插件可以指定props的类型、是否必穿
import { getContractList, setUserInfo } from '@/store/action'
import { Icon, Input, Button } from 'antd';
import './login.scss'
import $api from '@/api'
class Login extends Component {
  static propTypes = {
    // orderStatus: PropTypes.object.isRequired,
  }
  state = {
    codeImg: process.env.REACT_APP_ROOT + '/upload/captcha?t=' + Math.random(),
    login_name: '',
    password: '',
    verifyCode: ""
  }
  changeCode = () => {
    this.setState({
      codeImg: process.env.REACT_APP_ROOT + '/upload/captcha?t=' + Math.random()
    })
  }
  login = () => {
    let params = {
      login_name: this.state.login_name,
      password: this.state.password,
      verifyCode: this.state.verifyCode,
      action_id: '0c0a2b9cd16e9594774c2979951d709b4'
    }
    $api.post('/login/index', params).then(res => {
      if (res.code === 200) {
        this.props.setUserInfo(res.data)
        this.props.getContractList()
        // 跳转到首页
        this.props.history.push('/home')
      }
    })
  }
  componentDidMount () {
    // 
  }
  render () {
    return (
      <div className="loginbox">
        <Input
          value={this.state.login_name}
          type='text'
          prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
          onChange={(e) => {
            this.setState({
              login_name: e.target.value
            })
          }}
          placeholder="Username"
        />
        <Input
          value={this.state.password}
          prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
          type="password"
          placeholder="密码"
          onChange={(e) => {
            this.setState({
              password: e.target.value
            })
          }}
        />
        <Input
          value={this.state.verifyCode}
          prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
          onChange={(e) => {
            this.setState({
              verifyCode: e.target.value
            })
          }}
          placeholder="验证码"
        />
        <img src={this.state.codeImg} alt="" onClick={this.changeCode} />
        <Button type="primary" onClick={this.login} className="login-form-button" style={{ marginTop: '15px' }}>
          login
          </Button>
        <Button type="link" onClick={() => { this.props.history.push('/home') }} style={{ marginTop: '15px' }}>直接进去</Button>
      </div>
    );
  }
}

export default connect(state => {
  return { // 第一个参数是返回store的state
    initData: state.initData,
  }
},
  { // 这里是action,需要引入
    getContractList,
    setUserInfo
  })(Login); // Login为react的一个页面

页面里面通过this.props访问

这非页面文件读取state
比喻:xhr里面添加请求头,store.getState()即可

import axios from 'axios'
import qs from 'qs'

import store from '@/store/store';

// 配置根路径开发、线上等环境服务端的配置
let root = process.env.REACT_APP_ROOT
axios.defaults.baseURL = root
axios.defaults.withCredentials = true // 跨域
axios.defaults.timeout = 50000
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
// 请求拦截
axios.interceptors.request.use(
  // 请求头添加必要的参数
  config => {
    let initData = store.getState().initData
    config.headers.authKey = initData.authKey
    config.headers.sessionId = initData.sessionId
    return config
  },
  error => {
    console.error('请求拦截捕获到错误', error)
  }
)
// 响应拦截
axios.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code === 200) {
      return response
    } else {
      if (res.code === 101) {

      } else if (res.code === 600) {

      } else {
        console.error(res.error)
      }
      return Promise.reject(response.data)
    }
  },
  error => {
    return Promise.reject(error)
  }
)
export default {
  get: (urlName = '', params = {}, config = {}) => {
    return axios.get(urlName, { params, ...config }).then((res) => {
      return res.data
    }).catch((error) => {
      return error
    })
  },
  // 一般的post
  post: (urlName = '', params = {}, config = {}) => {
    return axios.post(urlName, qs.stringify(params), config).then((res) => {
      return res.data
    }).catch((error) => {
      return error
    })
  },
  // 直接传json格式的post
  jsonPost: (urlName = '', params = {}, ) => {
    return axios.post(urlName, params, {
      headers: {
        'Content-Type': 'application/json'
      }
    }).then((res) => {
      return res.data
    }).catch((error) => {
      return error
    })
  }
}

;