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
    })
  }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章