刚开始用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