这礼拜终于把手头的需求完结的差不多了,终于有时间沉淀下这段时间学习到的新技术了。
redux,我是在阮一峰大神的教程里学习的,先粘上传送门,大神在3篇连续的教程中,很详细的讲解了redux和React-Redux 的原理和用法,而我接下来主要是整理下我的学习笔记,和我在实践中走过的坑。
一.我对redux理解
众所周知,用react去做页面逻辑的时候,页面所见的所有dom元素,都可以被拆分成小的组件,如果遇到复杂页面,层级结构较多的话,组件state状态的统一管理就会很费劲,redux就是把页面所有需要共享的状态统一管理,存储到一个对象当中,如果在一个组件内部改变了这个state,那么所有组件用到这个state的地方都会随之做出改变,做到牵一发而动全身,不过要提醒大家一点注意的是,如果正如阮一峰大神的写在前言,对于简单的路由页面,是没有必要一定用redux来管理state的。
二.我的redux实践
在这里我举个简单栗子,就像我在做的控制台,整个项目用的react-router前端路由,所有子页面都是组件,左边是整体导航,右边是路由子页面,当用户切换右边tab页面的时候,父级组件的menu需要做高亮联动,之前这里实现,我是用prop-types库,祖先传参的方法实现的(具体实现参见上篇),这里用了redux管理状态的话,就会很简单的实现这个需求。
1.安装
cnpm install redux --save-dev
cnpm install react-redux --save-dev
cnpm install prop-types --save-dev
2.首先,需要在入口index.js即入口文件,创建全局的store存储状态对象,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
//React-Redux 提供Provider组件,可以让容器组件拿到state。
//它的原理是React组件的context属性
import { Provider } from 'react-redux';
// import './index.css';
import App from './template/App';
import registerServiceWorker from './registerServiceWorker';
//Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法
import reducers from './reducers/index';
// console.log(createStore)
//Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
const store = createStore(reducers);
const rootEl = document.getElementById('root');
// ReactDOM.render(<App />, document.getElementById('root'));
const render = () => ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootEl
);
render();
registerServiceWorker();
//设置监听函数,一旦 State 发生变化,就自动执行这个函数。
store.subscribe(render);
3.创建reducer处理器
我所理解的reducer就是一个switch处理器,根据不同的输入态,返回对应的输出态。
src/reducers/index.js
import { combineReducers } from 'redux'
import menuSwitch from './menuSwitch'
import counter from './counter'
const reducers = combineReducers({
menuSwitch: menuSwitch,
counter1: counter,
});
export default reducers
src/reducers/menuSwitch.js
const defaultState = 'containerTaskList';
const menuSwitch = (state = defaultState, action) => {
switch (action.type) {
case 'MENU_1':
return 'containerTaskList'
case 'MENU_2':
return 'containerContainerList'
default:
return state
}
}
export default menuSwitch
4.子页面逻辑
src/template/workshop/laboratory/Lab5.js
在子页面中,如此改变menu菜单的state
store.dispatch({ type: 'MENU_2' });
5.父页面逻辑
src/template/framework.js
在父级framework里的render函数里,用store.getState就可以获得改变的菜单state
render() {
let currentMenu1 = this.context.store.getState().menuSwitch;
return (
<Menu
selectedKeys={[currentMenu]}
</Menu>
)
}
三.踩坑
在react-router路由中,需要把路由配置部分单独提出来,不然会有如下报错,虽然是个warning,但是很烦人有木有啊!!
这里当真是折腾了我整整一晚上,才在Stack Overflow上找到了解决方案,新改后的路由配置部分如下,把routes单独提取到变量当中,并且把initFrameWork方法也单独提取出来了,不知道react-router4里有没有改进这个问题。
app.js
const initFrameWork = (nextState, replace, cb) =>{
//blabla
};
const routes = (
<Route path="/">
{/*重定向 到myHome*/}
<IndexRedirect to="/index" />
{/*登录*/}
<Route path="/login" getComponent={$util.lazyLoadComponent(require('./Login'))}></Route>
{/*主框架*/}
<Route path="/frameWork" getComponent={$util.lazyLoadComponent(require('./FrameWork'))} onEnter={initFrameWork}>
</Route>
)
class App extends Component {
state = {
};
render() {
return (
<Router history={browserHistory}>
{ routes }
</Router>
);
}
}
//blabla
};
const routes = (
<Route path="/">
{/*重定向 到myHome*/}
<IndexRedirect to="/index" />
{/*登录*/}
<Route path="/login" getComponent={$util.lazyLoadComponent(require('./Login'))}></Route>
{/*主框架*/}
<Route path="/frameWork" getComponent={$util.lazyLoadComponent(require('./FrameWork'))} onEnter={initFrameWork}>
</Route>
)
class App extends Component {
state = {
};
render() {
return (
<Router history={browserHistory}>
{ routes }
</Router>
);
}
}