Redux:
Redux是一个数据管理框架,经常和React搭配适用。由于React写大型复杂应用遇到困难,2014年Fackbook提出Flux架构的概念。2015年,Redux出现,将Flux与函数式编程结合在一起。
自带规范,将数据管理定义为三个核心概念,store, reducer,action。
另一数据管理框架Mobx简单自由,但缺乏规范,两者分别适用于不同的场景。
在学习Redux之前,先来弄清楚一个问题,也就是import 语句有时候带花括号{},有时候不带,有什么区别呢?如果适用不当,就会引起编译错误。
例如:有个文件user.js,有两种情况。
不使用{}
import user from './user';
对于这种情况,只有user.js文件提供默认的export default导出,而且必须是默认导出,这样就可以在其他文件中使用user了。在这种不使用{}来引用模块的情况下,import模块时的命名是随意的,即如下两种写法都是正确的,因为它总是会被解析到user.js中默认导出 export default。
import user2 from './user' (正确)
import anyUser from './user' (正确)
使用花括号{}来import
import {user} from './user';
上面代码生效的前提是,只有当模块user.js中有如下命名导出为user的export语句,如下:
export const user = 42;
并且,在声明了命名导出后,name在另一个js中使用{}引用模块时,import时的模块命名是有意义的。并且只能写成如下形式。
import {user} from './user'; (正确)
import {anyUser} from './user'; (错误)
一个js文件就是一个模块,一个模块只能有一个默认导出export default,但是可以有任意多的命名导出,你也可以一次性的将他们导入:
import user, {user2, anyUser} from './user';
上面代码使用默认导出的user,和命名导出user2,和anyUser。
- 下面进入Redux学习
先要引入redux包的支持,package.json中添加支持。
"dependencies": {
"react-redux": "~6.0.1",
"redux": "~4.0.1"
}
从redux的核心概念的第一个概念入手,也就是action。action需要有type和data,type也就是一个字符串,起到标识的作用,store中的action的type必须唯一才能。暂时先忽略store文件,暂时先理解成store是一个数据仓库,后面会对store进行构建。
import { store } from '../store';
// 定义了一个action的type
export const UPDATE_TITLE = 'UPDATE_TITLE';
// 定义了一个 function, function就是向store派发一个action,
// store会根据接收到的action去更新相应的数据
export function updateTitle(data) {
store.dispatch({
type: UPDATE_TITLE,
data: data
});
}
有了action,下面来写reducer,reducer的中文叫汇总器(我自己的翻译哈),也就是用于来处理action,告诉store怎样去处理数据。root reducer有很多个子 reducer构成,也就是最终的一个大的reducer,当来了一个action的时候,action会到每个子的reducer中去匹配,看看有没有对这个action的处理,如果有,则更新相应数据,返回一个全新的store数据仓库里的数据(引用必须发生变化才可以,通常用Object.assign({}, state, data)来实现引用变化)。
整个store构成的是一个树状结构的数据仓库。
reducers/title.js
import { UPDATE_TITLE } from '../actions/title';
const initialState = "click me!";
export default (state = initialState, action = {}) => {
const { type, data } = action;
switch(type) {
case UPDATE_TITLE:
return data;
default:
return state;
}
}
上面构建了一个reducer,对应的数据只是一个字符串,初始值是”click me!”,当收到action后,并且匹配到action,就把数据更新为传进来的值。
然后去把这些所有的reducer构建成一个root reducer。combineReducers就是把这些子的reducer 联合成一个reducer。
这个文件起名为index.js,是有特殊用途的,后面使用时import rootReducer from ‘../reducers’; 并不需要写成
import rootReducer from ‘../reducers/index’; 其实也就是一种简便的写法,个人认为老师讲解过程中并没有把这个说出来,新接触redux的同学会一头雾水。刚开始学的时候真的不建议各种简写,本来挺简单的东西,被这些奇怪的写法给绕进去了。如果这个文件起名不是index,比如叫root.js ,那么import的时候必须严格指定文件名。
reducers/index.js
import { combineReducers } from 'redux';
import titleReducer from './title';
const rootReducer = combineReducers({
title: titleReducer
// friend: friendReducer
});
export default rootReducer;
上面的代码和老师讲的写法不一样,就是为了更清楚,我更喜欢这样的写法,不喜欢很骚的写法,虽然少写了几行,但是可读性降低了,对新手来说就更要命了。
上面的代码中,我将title文件默认导出的reducer(一个function)命名为了titleReducer,来帮助理解消化。可以理解为:stroe里现在有一个节点,名字叫title,这个节点里对应的reducer是titleReducer,用来处理action引起的数据更新。当然store树里有可以有其他的节点,比如上面的friend节点。
有了action,reducer,rootReducer之后,就可以构建store了。注意下面import rootReducer from ‘../reducers’;的时候,并没有指定具体文件,而是只指定了文件夹,他会去找index文件。
下面的代码就是由指定的 rootReducer 去创建了一个store数据仓库。
中间件middlewares中的redux-logger 是方便开发人员调试,当dispatch action的时候,会在控制台上输出日志,方便跟踪调试。
import { compose, applyMiddleware, createStore } from 'redux';
import rootReducer from '../reducers';
const middlewares = [];
if(process.env.NODE_ENV === 'development') {
const { logger } = require('redux-logger');
middlewares.push(logger);
}
export const store = compose(applyMiddleware(...middlewares))(createStore)(rootReducer);
下面的代码 Provider 组件的属性名必须为store。到此为止,我们可以在App组件中肆意的玩耍store中的数据了。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root'));
App组件的代码:App组件里有两个子组件Friend和Home,一起来看一下,这两个组件是怎么访问或更新store中的数据的吧。
import React from 'react';
import './App.css';
import Friend from './views/friend';
import Home from './views/home';
class App extends React.Component {
render(){
return (<div>
<Friend/>
<Home/>
</div>);
}
}
export default App;
Home组件代码:
在Home组件中,点击Button的时候调用function updateTitle,然后把要更新的数据传入,将store中相应的数据更新。export 导出的时,用connect方法,返回组件的props,connect方法的参数state是store中的数据,可以选择自己感兴趣的数据。
需要注意的是:connect方法构造了Home组件的props,在Home组件中点击button调用updateTitle来dispatch派发一个action,reducer匹配到这个action,就会更新store中相应的数据。store中数据被更新了,Home组件中的props的值也会被更新,这就是为什么title发生变化的原因。
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { updateTitle } from '../../actions/title';
class Home extends Component {
constructor(props){
super(props);
this.state = {
value: ''
};
}
render() {
return (<div>
<input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}/>
<button onClick={() => {
if(this.state.value) {
updateTitle(this.state.value);
}
}}>Change</button>
<h2>{this.props.title}</h2>
</div>);
}
}
export default connect((state) => {
return {
title: state.title
};
})(Home);
Friend组件代码:
原理和Home组件一样。
import React from 'react';
import { addFriend, delFriend } from '../../actions/friend';
import { connect } from 'react-redux';
class Friend extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'a'
};
}
render() {
const { friendList } = this.props;
return (<div>
<input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})}/>
<button onClick={(e) => addFriend(this.state.value)}>Add</button>
{
friendList.map((item) => <p key={item.id}>{item.name}<button onClick={(e) => delFriend(item.id)}>Delete</button></p>)
}
</div>);
}
}
export default connect((state) => {
return {
friendList: state.friend.list
};
})(Friend);
- UI效果
当点击button Add和Delete的时候,可以看到console里有日志输出,这其实就是middlewares 提供的功能。
日志包含之前store的值,派发的action,和当前store的值。
点Add的日志:
点Delete的日志:
点Change的日志:**
函数组件中使用Redux
先来看代码:
它和上面的例子最明显的区别就是没有全局的store,Redux会帮我们定义全局store。
使用useReducer 函数,传入参加reducer和initialState, 返回一个state,也就是store的数据,还有dispatch方法,有了这个state就可以访问里面的数据,想更新数据的话可以用dispatch来派发一个action。 ```javascript import React, {useReducer} from ‘react’; import {render} from ‘react-dom’;
const initialState = { count: 1};
function reducer(state, action) { switch(action.type){ case ‘reset’: return initialState; case ‘increment’: return { count: state.count+1}; case ‘decrement’: return { count: state.count-1}; default: return state; } }
function Counter() { const [state, dispatch] = useReducer(reducer, initialState);
return (<div>
Count: {state.count}
<button onClick={() => dispatch({type: 'reset'})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>
Add
</button>
<button onClick={() => dispatch({type: 'decrement'})}>
Minus
</button>
</div>);
}
render(