中文文档:https://www.redux.org.cn/
结合 阮一峰 大神 redux 教程
1、适用场景:多交互、多数据源
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务区大量交互,或者使用了WebSocket
- View要从多个来源获取数据
2、设计思想
- Web应用是一个状态机,视图与状态是一一对应的
- 所有的状态,保存在一个对象里面
3、基本概念与API
- Action (可以理解为View)
State 的变化会导致 View 的变化,而 Action 就是 View 发出的通知,表示 State 要发生变化了。
也可以理解为: Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action ,它会运送数据到 Store 。
通过 store.dispatch() 方法将 action 传到 store。
- Action 创建函数(注意: “action” 和 “action 创建函数”是两个概念)
定义一个函数来生成 Action ,这个函数就叫 Action 创建函数
const ADD_TODO = '添加 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
上面的 addTodo 函数就是一个 Action 创建函数
Store 收到 Action 后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer 。
Reducer 是一个纯函数,接收旧的 State 和 Action 作为参数,返回一个新的 State。
const reducer = function (state, action) {
return newState
}
整个应用的状态的初始状态,可以作为 State 的默认值。例:
const initState = 0;
const reducer = (state = initState, action) => {
switch(action.type) {
case 'ADD':
return state + action.payload;
default:
return false;
}
}
const state = reducer(1, {
type: 'ADD',
payload: 2
})
// console.log(state) => 3
- 纯函数
纯函数是函数式编程的概念
1. 不得改写参数
2. 不能调用系统 I/O 的 API
3. 不能调用 Date.now() 或者 Math.random() 等不纯方法
由于 Reducer 是纯函数,可以保证同样的 State 可以得到同样的 View 。因此 Reducer 函数里面不能改变 State ,必须返回一个全新的对象,例:
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, {thingToChange})
// 或者
return {...stete, ...newState}
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
- Store 是保存数据的地方,整个应用只有一个 Store。职责:
1. 维持应用的 State
2. 提供 getState() 方法获取 state
3. 提供 dispatch(action) 方法更新 state
4. 通过 subscribe(listener) 注册监听器
5. 通过 subscribe(listener) 返回的函数注销监听器
根据已有的 reducer 来创建 store 非常容易
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
// createStore(fn)
createStore 函数接受另一个函数作为参数,返回新的 Store 对象。
注:更多详细的内容请看官网或者阮一峰大神的redux入门教程
例: 登录案例
描述: 使用 redux 进行登录状态的管理,使用 redux-persist 进行持久化存储,react-redux 与 redux 一起使用,react-navigation 页面之间的跳转
目录结构
- ActionTypes.js
'use strict'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const LOGOUT_ERROR = 'LOGOUT_ERROR';
- LoginAction.js
'use strict'
import {
LOGIN_SUCCESS,
LOGIN_ERROR,
LOGOUT_SUCCESS,
LOGOUT_ERROR
} from './ActionTypes'
export function loginSuccess(user) {
return {
type: LOGIN_SUCCESS,
isLogin: true,
message: '登录成功',
user
}
}
export function loginError(user) {
return {
type: LOGIN_ERROR,
isLogin: false,
message: '登录失败',
user
}
}
export function logoutSuccess(user) {
return {
type: LOGOUT_SUCCESS,
isLogin: false,
message: '退出成功',
user
}
}
export function logoutError(user) {
return {
type: LOGOUT_ERROR,
isLogin: false,
message: '退出失败',
user
}
}
- LoginReducer.js
'use strict'
import _ from 'lodash'
import {
LOGIN_ERROR,
LOGIN_SUCCESS,
LOGOUT_ERROR,
LOGOUT_SUCCESS,
} from '../actions/ActionTypes'
export default function loginReducer(state = null, action) {
switch(action.type) {
case LOGIN_SUCCESS:
return _.assign({}, state, {
isLogin: true,
message: '登录成功',
user: action.user,
})
case LOGIN_ERROR:
return _.assign({}, state, {
isLogin: false,
message: '登录失败',
user: action.user,
})
case LOGOUT_SUCCESS:
return _.assign({}, state, {
isLogin: false,
message: '退出成功',
user: null,
})
case LOGOUT_ERROR:
return _.assign({}, state, {
isLogin: false,
message: '退出失败',
user: action.user,
})
default:
return state
}
}
- RootReducer.js
'use strict'
import { combineReducers } from 'redux'
import LoginReducer from './LoginReducer'
const rootReducer = combineReducers({
LoginReducer,
})
export default rootReducer
- store.js
import { createStore } from 'redux'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage' //defaults to localStorage for web and AsyncStorage for react-native
import RootReducer from './reducers/RootReducer'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, RootReducer)
export const store = createStore(persistedReducer);
export const persistor = persistStore(store);
- App.js
import React, {Component} from 'react';
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './redux/store'
import { createStackNavigator, createAppContainer } from 'react-navigation'
import Home from './src/Home'
import Login from './src/Login'
const RootStack = createStackNavigator(
{
Home,
Login,
},
{
initialRouteName: 'Home',
defaultNavigationOptions: {
header: null
}
}
)
const AppContainer = createAppContainer(RootStack);
export default class Navigation extends Component {
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<AppContainer/>
</PersistGate>
</Provider>
)
}
}
- Home.js
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
Button,
} from 'react-native';
import { connect } from 'react-redux'
import { logoutSuccess } from '../redux/actions/LoginAction'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
user: {}
}
}
componentDidMount() {
let user = this.props.user;
this.setState({user})
}
render() {
let user = this.props.user;
return (
<View style={styles.container}>
<Text style={styles.welcome}>登录状态: {user ? '已登录' : '未登录'}</Text>
{
user ?
<View>
<Text style={styles.welcome}>姓名:{user.name}</Text>
<Text style={styles.welcome}>年龄:{user.age}</Text>
</View>
:
null
}
{
user ?
<View>
<Button title={'退出'}
onPress={()=>this.props.dispatch(logoutSuccess())}
/>
<Button title={'前往登录页修改信息'}
onPress={()=>this.props.navigation.navigate('Login')}
/>
</View>
:
<Button title={'前往登录'}
onPress={()=>this.props.navigation.navigate('Login')}
/>
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
function selected(state) {
return user = state.LoginReducer;
}
export default connect(selected)(Home)
- Login.js
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
Button,
} from 'react-native';
import { connect } from 'react-redux'
import { loginSuccess } from '../redux/actions/LoginAction'
class Login extends Component {
constructor(props) {
super(props);
this.state = {
user: {
name: '',
age: '',
}
}
}
componentDidMount() {
let user = this.props.user;
if(user) {
this.setState({user})
}
}
render() {
let user = this.state.user;
return (
<View style={styles.container}>
<TextInput
placeholder={'姓名'}
defaultValue={user.name}
style={styles.input}
onChangeText={(text) => this._change('name',text)}
/>
<TextInput
placeholder={'年龄'}
defaultValue={user.age}
style={{borderWidth:1,borderColor:'gray',padding:5,marginBottom:2}}
onChangeText={(text) => this._change('age',text)}
/>
<Button title={'登录'}
onPress={this._login}
/>
<Button title={'前往首页'}
onPress={()=>this.props.navigation.navigate('Home')}
/>
</View>
);
}
_change = (key,value) => {
let user = this.state.user;
user[key] = value;
this.setState({user});
}
_login = () => {
let user = this.state.user;
this.props.dispatch(loginSuccess(user));
this.props.navigation.push('Home')
}
}
const styles = StyleSheet.create({
container: {
marginTop: 50,
paddingHorizontal: 20,
backgroundColor: '#F5FCFF',
},
input: {
borderWidth: 1,
borderColor: 'gray',
padding: 5,
marginBottom: 2,
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
function selected(state) {
return user = state.LoginReducer;
}
export default connect(selected)(Login)
实现效果