在(1)中我讲了一个电灯的例子,用开关来控制等的状态的变化,
现在把它用react-native/redux来实现了,比counter还要简单,只有一个状态的改变,都已经写好了,还可以作为一个框架来使用。安装了redux-logger以后,可以在按下按钮的时候实时的看到状态的变化。同时结合在UI组件中的console.log的方法,基本解决了redux调试的方法问题。在Component中导入了react-native-elements组件库。这个和bootstrap的效果类似,封装了很多的组件可以直接来使用,这个组件库可以换为native-base组件库。在组件中修改UI 完全不改变app的交互操作的逻辑。就这个简单的app就做了一下午,主要原因是一个也是初学,另外一个问题为了调试加进去了好多的console.log(),完全搞乱的出现问题的原因,但是这个坑还是值得的。主要的目的就是给需要学习redux的同学提供一个简单的教程。redux的学习也要不断的反复拿捏才行,理解以后其实觉得redux的思想其实很简单,但是要发生这个转变需要一定的时间,当然有基础的高手除外。
界面就是这么一个,有一个灯,下面有一个开关,要解决的问题就是按下按钮的操作使灯的状态发生变化,Component里面有两个文件,一个是直接在组件中操作,另外一个就是我们这里的主题,组件中操作以后,state在redux中绕了一圈以后回到组件中,组件的状态就发生了变化,这里组件和redux是分开的,在学习的时候等你体会到这种分开的情况就离理解差不多了。
要时刻注意的是流程,组件和redux的衔接
1. 组件的操作是怎么在Redux中对上暗号,接上头的
2.Redux中状态发生改变之后又是怎么返回的组件的
以上两点就是connect的内容,关键就在这里。
下面是文件的结构,稍等我放到github上去。
下面先从视觉组件开始
先贴一下直接在组件中操作的逻辑例子
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
ListView,
} from 'react-native';
import * as lampAction from '../actions/lamp';
//import {Container, Header, Title, Content,Button } from 'native-base';
import {
Button
} from 'react-native-elements'
class Lamp extends Component {
constructor(props) {
super(props);
this.state = { //初始化状态
light: false
}
}
componentDidMount() {}
shut() { //组件中直接操作的函数,负责改变状态
this.setState({
light : !this.state.light //状态发生翻转
})
}
render() {
this.shut=this.shut.bind(this);
//console.log(light); //可以在组件中打印state
//light==false?(uri=' ./image/off.png'):(uri=' ./image/on.png');
var status = this.state.light==false
? require('./image/off.png')
: require('./image/on.png');
return (
<View style={styles.container}>
<Image source={status}
style={styles.customimg}
/>
<Button
style={styles.buttonMargin}
raised
onPress={ this.shut}
title='开关' />
</View>
);
}
}
var styles=StyleSheet.create({
//样式省略
});
export default Lamp;
下面是作为导入redux以后的组件代码
//Lamp/src/component/Lamp.js
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
ListView
} from 'react-native';
import {
Button,
Icon,
SocialIcon
} from 'react-native-elements'; //导入的elements组件库
//你可以选择其他的组件库,加速开发流程。
class Lamp extends Component {
constructor(props) {
super(props);
componentDidMount() {}
render() {
//下面这一句是理解的难点,在react中组件都是在使用的时
//候,通过注入props来获取一些参数,下面这两个参数,一个是操
//作的方法名,一个是灯的状态,初学的时候很难理解这两个东西
//到底是从哪里来的。其实有点远,组件获取这两个东西的地方是
//在container文件夹中,container文件其实做的内容很少,就是
//要建立组件和Redux之间的联系,这两个东西也就是在那里注入进来的
const { switchLamp,light} = this.props;
console.log(light.light); //这个语句可以随时监测light的状态是
//false还是true.
//下面这个三元判断就是根据light的状态来决定加载哪一张图片
var status =light.light==false
? require('./image/off.png')
: require('./image/on.png');
return (
<View style={styles.container}>
<Image source={status}
style={styles.customimg}/>
<Icon
raised
name='heartbeat'
type='font-awesome'
color='#f50'
//这个地方的switchLamp是函数,然而这个函数并不在这里执
//行,我在container里面吧这个函数绑定到了dispatch上
//这样,执行这个函数,绑定地就会执行一个dispatch(),dispatch的意思
//是分发,那么分发这个函数到什么地方呢?这个函数和action
//中一个函数名字是一样的,这样发生在组件中的函数动作就会和
//redux中的函数名配对了,redux就感知到了组件要干什么,所谓
//对上眼了,对上暗号了。这里我们约定好函数名只由redux中的
//action来定义,组件中直接来用,这样就不会发生错误匹配的问题
//这里的onPress函数式唯一和操作逻辑相关的地方,如果修改组
//件样式,或者更换组件,只保留这个函数就可以了,其他的都可
//样式和逻辑彻底分开了,当然这是理想化的,实际操作的是后,
//也是挺复杂的。
onPress={() => switchLamp()} />
</View>
);
}
}
var styles=StyleSheet.create({
//样式代码省略
});
export default Lamp;
既然上面的函数已经和Redux已经对上眼了,我们就马上看看,
他们是怎么对上的
//lamp/src/containers/App.js
import { bindActionCreators } from 'redux'; //用来绑定函数和dispatch()
import { connect } from 'react-redux'; //redux和component的中间代理人
import Lamp from '../components/Lamp'; //这就是上面讲的组件
import * as switchLamp from '../actions/lamp'; //获取actions中
//定义的action函数,这里并不是要这些函数做什么,而是给组件一套对暗号的密码表,
//mapStateToProps,看名字就是State转变为Props,state是在
//redux中的,组件在使用的时候是通过props来获取参数的,
//所以这里有这么一个转变的过程
function mapStateToProps(state, props) {
//console.log(state);
return {
light:state.light //这里的state比较简单,可以很复杂
};
}
//这里把方法也转为props,以供组件使用
function mapDispatchToProps(dispatch) {
//使用bindActionCreators来绑定dispatch和函数
//这是switchLamp看起来是一个函数,其实是一个对象,
//包含了actions里定义的所有函数
//组件中的函数就会自动dispatch到actions中和对应的函数想匹配,dispatch以后redux就接管了后续的逻辑操作
return bindActionCreators(switchLamp, dispatch);
}
//connect是我们这个系列要讲的核心,所有的内容其实都是围绕
//他,尽管这一句,但是是非常重要的,经过这么connect以后
//组件就获取了所需的方法名和props,
//connect其实是很灵活的,没有规定只能connect一次,
//所有的组件都可以用他来包装,看其他代买的时候要注意
//因为一旦程序规模变大,action里面的函数就不好一次注入了,
//在不同的组件中注入自己需要的函数和props是很好的选择。
export default connect(mapStateToProps, mapDispatchToProps)(Lamp);
组件dispatch 一个函数以后,redux就接管了后续的操作。 进入到actions中
//actions/lamp.js
export const SWITCH = 'SWITCH'; //这个常量是给reducer用的
//在redux中有几个地方可以打印state看看,但是这个文件中
//绝对不能打印state
import React, { Component } from 'react';
import {SWITCH} from '../constants/ActionTypes';//也可以在这里定义常量
//下面的这个函数和组件dispatch的函数名是一样的时候,就会执
//行操作,这个操作拿着type的类型去操作state的改变。
//你看凡是逻辑分离的地方都要对这个信号
export function switchLamp() {
return {
type: SWITCH,
};
};
上面的type 就会和redcer匹配,reducer的作用是改变state,
state对象其实就相当于一个数据库,用来存储app的所有变化。
在redux中把state定义为一个🌲,树有各个部分,果实可以吃,
叶子可以做药材,树皮可以做电缆,树干可以盖房子,树根可以做根雕。 当你要吃果实的时候只需要去摘果实就可以了,并不需要把整个树都弄到,所以需要进行过滤。 这个过程是在container中来完成的。我们的这个实例比较简单,只是一颗种子,么办法过滤。
下面就看看reducer做了什么工作
//lamp/reducers/lamp.js
//先导入actiontype,这样从aciton过来的动作,reducer就知道要干什么
import { SWITCH } from '../constants/ActionTypes';
//初始化一颗树,先种下一颗种子,随着程序的变大,这棵树也变大
const initialState = {
light: false
};
//实际的操作就在这里了,
export default function light(state=initialState, action) {
switch (action.type) { //进行匹配
case SWITCH: //匹配上了
return {
light:!state.light, // 返回一个新的状态,取反就可以了
//ui 组件中就可以根据这个变化来改变状态
};
default:
return state;
};
}
//有时候程序较复杂,reducer写在一起很难理解,所以就会分开
//最终会合并成一个
//lamp/reducers/index.js
import { combineReducers } from 'redux';
//import film from './film';
import light from './lamp';
const rootReducer = combineReducers({
light
});
export default rootReducer;
组件所需要的逻辑操作就在reducers中完成了。
reducer改变的逻辑交给store,这个store其实不实际干活,而是
一个发号施令的角色。所有的操作和所有的状态变化他都知道,
所以我们可以在这里监控整个程序的变化,redux-logger在store里面注入就可以监视变化了
//看起来比较复杂
//这里面还注入了redux-sage这个是用来进行异步操作的逻辑
//在这里涉及不到。
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import rootReducer from '../reducers/index'; 注入reducer到store
const middlewares = [];
const createLogger = require('redux-logger');
// configuring saga middleware
const sagaMiddleware = createSagaMiddleware();
middlewares.push(sagaMiddleware);
if (process.env.NODE_ENV === 'development') {
const logger = createLogger();
middlewares.push(logger);
}
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);
// install saga run
store.runSaga = sagaMiddleware.run;
store.close = () => store.dispatch(END);
return store;
}
下面我们在返回container,reducer中改变的状态有要返回组件中
我把container的代码再粘贴一遍,但是注意这一次的container
和刚开始的container实际是不同的,state发生了改变了。
//lamp/src/containers/App.js
import { bindActionCreators } from 'redux'; //用来绑定函数和dispatch()
import { connect } from 'react-redux'; //redux和component的中间代理人
import Lamp from '../components/Lamp'; //这就是上面讲的组件
import * as switchLamp from '../actions/lamp'; //获取actions中
//定义的action函数,这里并不是要这些函数做什么,而是给组件一套对暗号的密码表,
//mapStateToProps,看名字就是State转变为Props,state是在
//redux中的,组件在使用的时候是通过props来获取参数的,
//所以这里有这么一个转变的过程
function mapStateToProps(state, props) {
//console.log(state);
return {
light:state.light //这里的state比较简单,可以很复杂
};
}
//这里把方法也转为props,以供组件使用
function mapDispatchToProps(dispatch) {
//使用bindActionCreators来绑定dispatch和函数
//这是switchLamp看起来是一个函数,其实是一个对象,
//包含了actions里定义的所有函数
//组件中的函数就会自动dispatch到actions中和对应的函数想匹配,dispatch以后redux就接管了后续的逻辑操作
return bindActionCreators(switchLamp, dispatch);
}
//connect是我们这个系列要讲的核心,所有的内容其实都是围绕
//他,尽管这一句,但是是非常重要的,经过这么connect以后
//组件就获取了所需的方法名和props,
//connect其实是很灵活的,没有规定只能connect一次,
//所有的组件都可以用他来包装,看其他代买的时候要注意
//因为一旦程序规模变大,action里面的函数就不好一次注入了,
//在不同的组件中注入自己需要的函数和props是很好的选择。
export default connect(mapStateToProps, mapDispatchToProps)(Lamp);
至此组件中的操作数据流程就完成了。
这个是比较简单的,但是redux的基本流程和框架就是这样的。
我在(1)中讲了,因为组件和redux是解耦和,改变任意一个,另一个不发生变化就可以很好的工作。下面我们能不能再进一步?
灯的开关也可以换成声控的,有声音以后会延迟一段时间再灭,
如果继续有声音,灯还会亮着。能实现吗?