Redux 一个还好的redux 实践吧

619 阅读4分钟

刚开始用redux 感觉它有点笨笨的;

因为重复代码非常多啊;

以官方例子说吧:(并不是说官网例子不好,对刚起步用它的人来说,封装太多反而难以理解)

1.对于store中的一个值的改变需要写两个方法也是有点啰嗦;

2.算上action中的type 和 和reducer中的switch,加起来每写一个action都要写3,4次相同或者类似的名字也是很烦的;

3.每次更新状态都要 dispatch(action(xxx))或者手动封装...


然后自己动手撸了一点小方法,改善了一下

这个实践最后在组件中的使用如下:

假设有一个store模块结构如下:

 {  
    songSheet: {
        name: "推荐歌单", 
        list: [], 
        active: false, 
        isFetching: false,
        ...
    },  
    ...
    placeConf:{
        personal: {name: "私人FM", show: false,...},
        ...
    }
}

然后再reducer的分支处理函数中如下设置:

setPlaceConfPersonalVis(state, action){return copy(state,{
    placeConf:{personal:{show: state.data}}
})}

然后mapDispatchToProps方法中这么写 :

const mapDispatchToProps = dispatch => {
  const $actions = makeActions(dispatch);
  $action.setPlaceConfPersonalVis(true);  return {
    ...actions ,
    // ...  其他方法
  }
}

是的,简化后使用就是这么简单。

然后如下是各文件代码:

reducers/index.js // 合并 各reducer模块

import { combineReducers } from 'redux';
import cmusichome from './cmusichome';
export default combineReducers({
  cmusichome,
  // 这里可以放其他模块的 reducer
});

reducers/cmusichome.js // 被 reducers/index.js引用 // 核心之一, 将reducer的分支方法拆分出去;

// 这个是分支处理函数
import cmusichomeFn from "./cmusichomeFn.js";
// 这里是reducer // 生成并更新store 的入口函数
const cmusichome = (state, action) => {
  // 初始化时, 没有state 所以可以在这里设置 初始值
  state || (state= {
    songSheet: {name: "推荐歌单", list: [], active: false, isFetching: false,},
    songList: {name: "最新音乐", list: [], active: false, isFetching: false,},
    djprogramList: {name: "主播电台", list: [], active: false, isFetching: false,},
    playList:{name: "歌曲列表", list: [], show: false, isFetching: false,},
    recmendList:{show: false, list:[]},
    placeConf:{
      personal: {name: "私人FM", show: false},
      recmend: {name: "每日推荐", show: false},
      songList: {name: "歌单", show: false},
      rankingList: {name: "排行榜", show: false},
    }
  });
  const curFn = cmusichomeFn[action.type];
 // 根据action 查找分支函数
  return curFn && curFn(state, action) || state;
 // 没找到处理函数 则返回上一次的 state
};
export default cmusichome;

reducers/cmusichomeFn.js,// 节选,可以自己添加一些处理分支 被cmusichome.js引用

import tools from "../components/tools.js";
const {copy} = tools;
export default {
  // 歌单
  updateSongSheet(state, action) {
    return copy(state, {songSheet: {list: action.data,}});
  },
  toggleSongSheetState(state, action){
    return copy(state, {songSheet: {active: !state.songSheet.active,}});
  },
  fetchSongSheet(state,action){
    return copy(state, {songSheet: {isFetching: action.data,}});
  },
};

actions/index.js // 被views/Discover.js容器组件引用 // 核心之一,根据reducerFn的键名,创建同名action方法;

// 所有在 reducer 中使用的分支函数;
import cmusichomeFns from "../reducers/cmusichomeFn";
// ...这里可以继续引入其他模块的分值函数

// 根据reducer 分支函数名, 创建对应的 action 函数
// 这些action函数会接受一个值, 然后生成 返回{type: action名, data: 参数值}的方法;
function  makeReducerFnsIntoActions(reducerFns){
  const actions = {};
  Object.keys(reducerFns).forEach(item => actions[item] = data => ({type: item, data}));
  return actions;
};
// 组合所有actions 作为 makeActions 内部需要转化的actions
const actions = {
  ...makeReducerFnsIntoActions(cmusichomeFns),
  // ...这里可以继续添加 分支函数
};
// 导出将 dispatch(action(val)) 转化为 xxx.action(val) 模式的函数;
// 可以在外界用 ...xxx 解构成 action(val); 接受 dispatch 作为参数;
// 然后在 mapDispatchToProps 方法中 return { ...makeActions(dispatch), ... } 
// 就可以在 子组件中使用xxx.action(val) 真是极方便的;
export default function makeActions(dispatch){
  function _(str,val){dispatch(actions[str](val));};
  const actionFn = {};
  Object.keys(actions).forEach(item=> actionFn[item] = val => _(item,val));
  return actionFn;
}

App.js // 含react-router / react-redux

import React from 'react';
import logo from '../images/logo.svg';
import './App.css';
import {HashRouter,Route} from 'react-router-dom';
import Discover from './Discover.js';
const App = () => (
  <HashRouter basename="/">
    <div className="App">
      <img src={logo} className="App-logo" alt="logo" />
      <audio src="" id="audio"></audio>
      <Route path="/discover" component={Discover}/>
    </div>
  </HashRouter>
);
export default App;


views/Discover.js // 容器组件 // 被App.js 引用

import makeActions from '../actions';
import $http from "./../http/http.js";
import React, from 'react';
import { connect } from 'react-redux';
import Home from "./Home/Home.js";
const mapStateToProps = state => ({...state.cmusichome});
const mapDispatchToProps = dispatch => {
  const $actions = makeActions(dispatch);
  $http.personalized()(res => {
    $actions.updateSongSheet(
      res.result.map(item => ({
        name: item.name, img: item.picUrl, id: item.id,
      }))
    )
  });
  return{
    ...$actions,// 貌似到子组件 const {$action} = props;会好一些
    getPlayList: function (id) {
      $actions.fetchPlayList(true);
      $http.playlistDetail({id})(res=>{
        $actions.updatePlayList(res.playlist.tracks);
        $actions.togglePlayList(true);
        $actions.fetchPlayList(true);
      });
    },
    playASong(id){
      $http.songUrl({id})(res=>{
        const audio = document.querySelector("#audio");
        audio.src = res.data[0].url;
        audio.play();
      })
    },
  }};
export default connect(mapStateToProps, mapDispatchToProps)(Home);

copy.js // 被 reducers/cmusichomeFn.js 引用 // 核心之一 // 深拷贝数组外的属性 // 对于数组的操作是: 通过数组map返回新数组替换之前的数组,并不会直接引用传入的数组或者原store中的值(为什么这么做我也不知道);

import type from "./type.js";
function _copy() {
  if(arguments.length) {
    const list = Array.prototype.map.call(arguments, item => item);
    const curObj = list.shift();
    Object.keys(curObj).forEach(item=>{
      const val = curObj[item];
      if(val !== undefined)
        this[item] = (type.isObject(val) && copy(this[item]||{},val)) ||
          (type.isArray(val) && val.map(item => item)) ||
          val;
    });
    return _copy.apply(this, list);
  } else return this;
}
function copy(){return _copy.apply({},arguments);}
export default copy;

type.js // 被 copy.js 引用

const getType = item => (Object.prototype.toString.call(item).slice(8, -1));getType.isNumber = item => getType(item) === 'Number';
getType.isString = item => getType(item) === 'String';
getType.isArray = item => getType(item) === 'Array';
getType.isObject = item => getType(item) === 'Object';
getType.isBoolean = item => getType(item) === 'Boolean';
getType.isNull = item => getType(item) === 'Null';
getType.isUndefined = item => getType(item) === 'Undefined';
getType.isFunction = item => getType(item) === 'Function';
getType.isDate = item => getType(item) === 'Date';
export default getType;

demo 请移架 https://github.com/DeyaoCai/cmusic