redux

redux理解

redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action,store,reducer

  1. 是什么? redux是专门做状态管理的独立第3方库, 不是react插件
  2. 作用? 对应用中状态进行集中式的管理(写/读)
  3. 开发: 与react-redux, redux-thunk等插件配合使用

Redux 是一个 数据管理中心,可以把它理解为一个全局的 data store 实例
它通过一定的使用规则和限制,保证着数据的健壮性、可追溯和可预测性。它与 React 无关,可以独立运行于任何 JavaScript 环境中,从而也为同构应用提供了更好的数据同步通道。

核心理念:

  • 单一数据源: 整个应用只有唯一的状态树,也就是所有 state 最终维护在一个根级 Store 中;
  • 状态只读: 为了保证状态的可控性,最好的方式就是监控状态的变化。那这里就两个必要条件:
    Redux Store 中的数据无法被直接修改;
    严格控制修改的执行;
  • 纯函数: 规定只能通过一个纯函数 (Reducer) 来描述修改;

什么情况下需要使用 redux

  1. 总体原则: 能不用就不用, 如果不用比较吃力才考虑使用
  2. 某个组件的状态,需要共享
  3. 某个状态需要在任何地方都可以拿到
  4. 一个组件需要改变全局状态
  5. 一个组件需要改变另一个组件的状态

redux工作流程

  • view用actionCreator创建一个action,里面可能包含一些数据
  • 使用store的dispatch方法将acion传入store
  • store将action与旧的state转发给reducer
  • reducer深拷贝state,并返回一个新的state给store
  • store接收并更新state
  • 使用store.subscribe订阅更新,重新render组件

redux相关API

redux中包含: createStore(), applyMiddleware(), combineReducers()
store对象: getState(), dispatch(), subscribe()
react-redux: , connect()()

createStore()

  1. 作用:
    创建包含指定 reducer 的 store 对象
  2. 编码:
import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)

store 对象

  1. 作用:
    redux 库最核心的管理对象
  2. 它内部维护着:
    state
    reducer
  3. 核心方法:
    getState()
    dispatch(action)
    subscribe(listener)
  4. 编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)

applyMiddleware()

  1. 作用:
    应用上基于 redux 的中间件(插件库)
  2. 编码:
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' // redux 异步中间件
const store = createStore(
counter,
applyMiddleware(thunk) // 应用上异步中间件
)

combineReducers()

  1. 作用:
    合并多个 reducer 函数
  2. 编码:
export default combineReducers({
	user,
	chatUser,
	chat
})

redux 的三个核心概念

action

  1. 标识要执行行为的对象
  2. 包含 2 个方面的属性
    type: 标识属性, 值为字符串, 唯一, 必要属性
    xxx: 数据属性, 值类型任意, 可选属性

action默认是对象(同步action), {type: ‘xxx’, data: value},需要通过对应的actionCreator产生,,它的值也可以是函数(异步action), 需要引入redux-thunk才可以

  1. 例子:
const action = {
	type: 'INCREMENT',
	data: 2
}
  1. Action Creator(创建 Action 的工厂函数)
const increment = (number) => ({type: 'INCREMENT', data: number})

reducer

  1. 根据老的 state 和 指定的action, 返回新的 state 的纯函数(不能修改老的state)
  2. 样例
export default function counter(state = 0, action) {
	switch (action.type) {
		case 'INCREMENT':
			return state + action.data
		case 'DECREMENT':
			return state - action.data
		default:
			return state
	}
}
  1. 注意
    a. 返回一个新的状态
    b. 不要修改原来的状态

store

redux最核心的管理对象
内部管理着: state和reducer, 将 state,action 与 reducer 联系在一起的对象

如何得到此对象?

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
  1. 此对象的功能?
    getState(): 得到 state
    dispatch(action): 分发 action, 触发 reducer 调用, 产生新的 state
    subscribe(listener):

使用 redux 编写应用

下载依赖包

npm install --save redux

redux/action-types.js

/*
action 对象的 type 常量名称模块
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

redux/actions.js

/*
action creator 模块
*/
import {INCREMENT, DECREMENT} from './action-types'
export const increment = number => ({type: INCREMENT, number})
export const decrement = number => ({type: DECREMENT, number})

redux/reducers.js

/*
根据老的 state 和指定 action, 处理返回一个新的 state
*/
import {INCREMENT, DECREMENT} from '../constants/ActionTypes'
import {INCREMENT, DECREMENT} from './action-types'
export function counter(state = 0, action) {
	console.log('counter', state, action)
	switch (action.type) {
		case INCREMENT:
			return state + action.number
		case DECREMENT:
			return state - action.number
		default:
		return state
	}
}

components/app.jsx

/*
应用组件
*/
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import * as actions from '../redux/actions'

export default class App extends Component {

	static propTypes = {
	store: PropTypes.object.isRequired,
}

increment = () => {
	const number = this.refs.numSelect.value * 1
	this.props.store.dispatch(actions.increment(number))
}

decrement = () => {
	const number = this.refs.numSelect.value * 1
	this.props.store.dispatch(actions.decrement(number))
}

incrementIfOdd = () => {
	const number = this.refs.numSelect.value * 1
	let count = this.props.store.getState()
	if (count % 2 === 1) {
		this.props.store.dispatch(actions.increment(number))
	}
}

incrementAsync = () => {
	const number = this.refs.numSelect.value * 1
	setTimeout(() => {
		this.props.store.dispatch(actions.increment(number))
	}, 1000)
}

render() {
	return (
	<div>
		<p>
			click {this.props.store.getState()} times {' '}
		</p>
<select ref="numSelect">
	<option value="1">1</option>
	<option value="2">2</option>
	<option value="3">3</option>
	</select>{' '}
<button onClick={this.increment}>+</button>
{' '}
<button onClick={this.decrement}>-</button>
{' '}
<button onClick={this.incrementIfOdd}>increment if odd</button>
{' '}
<button onClick={this.incrementAsync}>increment async</button>

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import App from './components/app'
import {counter} from './redux/reducers'
// 根据 counter 函数创建 store 对象
const store = createStore(counter)
// 定义渲染根组件标签的函数
const render = () => {
	ReactDOM.render(
		<App store={store}/>,
		document.getElementById('root')
	)
}
// 初始化渲染
render()
// 注册 ( 订阅 ) 监听 , 一旦状态发生改变 , 自动重新渲染
store.subscribe(render)

问题

  1. redux 与 react 组件的代码耦合度太高
  2. 编码不够简洁

react-redux

理解

  1. 一个 react 插件库
  2. 专门用来简化 react 应用中使用 redux

React-Redux 将所有组件分成两大类

  1. UI 组件
    只负责 UI 的呈现,不带有任何业务逻辑
    通过 props 接收数据(一般数据和函数)
    不使用任何 Redux 的 API
    一般保存在 components 文件夹下
  2. 容器组件
    负责管理数据和业务逻辑,不负责 UI 的呈现
    使用 Redux 的 API
    一般保存在 containers 文件夹下

相关 API

  1. Provider
    让所有组件都可以得到 state 数据
<Provider store={store}>
	<App />
</Provider>
  1. connect()
    用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
connect(
	mapStateToprops,
	mapDispatchToProps
)(Counter)
  1. mapStateToprops()
    将外部的数据(即 state 对象)转换为 UI 组件的标签属性
const mapStateToprops = function (state) {
return {
	value: state
	}
}
  1. mapDispatchToProps()
    将分发 action 的函数转换为 UI 组件的标签属性
    简洁语法可以直接指定为 actions 对象或包含多个 action 方法的对象

使用 react-redux

  1. 下载依赖包
npm install --save react-redux
  1. redux/action-types.js
    不变
  2. redux/actions.js
    不变
  3. redux/reducers.js
    不变
  4. components/counter.jsx
/*
UI 组件 : 不包含任何 redux API
*/
import React from 'react'
import PropTypes from 'prop-types'
export default class Counter extends React.Component {
	static propTypes = {
		count: PropTypes.number.isRequired,
		increment: PropTypes.func.isRequired,
		decrement: PropTypes.func.isRequired
}
increment = () => {
	const number = this.refs.numSelect.value * 1
	this.props.increment(number)
}
decrement = () => {
	const number = this.refs.numSelect.value * 1
	this.props.decrement(number)
}
incrementIfOdd = () => {
	const number = this.refs.numSelect.value * 1
	let count = this.props.count
	if (count % 2 === 1) {
		this.props.increment(number)
	}
}
incrementAsync = () => {
	const number = this.refs.numSelect.value * 1
	setTimeout(() => {
		this.props.increment(number)
	}, 1000)
}
render() {
	return (
	<div>
		<p>
			click {this.props.count} times {' '}
		</p>
		<select ref="numSelect">
			<option value="1">1</option>
			<option value="2">2</option>
			<option value="3">3</option>
		</select>{' '}
		<button onClick={this.increment}>+</button>
		{' '}
		<button onClick={this.decrement}>-</button>
		{' '}
		<button onClick={this.incrementIfOdd}>increment if odd</button>
		{' '}
		<button onClick={this.incrementAsync}>increment async</button>
	</div>
	)
	}
}

containters/app.jsx

/*
包含 Counter 组件的容器组件
*/
import React from 'react'
// 引入连接函数
import {connect} from 'react-redux'
// 引入 action 函数
import {increment, decrement} from '../redux/actions'
import Counter from '../components/counter'
// 向外暴露连接 App 组件的包装组件
export default connect(
	state => ({count: state}),
	{increment, decrement}
)(Counter)

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import App from './containers/app'
import {counter} from './redux/reducers'
// 根据 counter 函数创建 store 对象
const store = createStore(counter)
// 定义渲染根组件标签的函数
ReactDOM.render(
(
	<Provider store={store}>
		<App />
	</Provider>
),
document.getElementById('root'

问题

  1. redux 默认是不能进行异步处理的,
  2. 应用中又需要在 redux 中执行异步任务(ajax, 定时器)

redux 异步编程

下载 redux 插件( 异步中间件)

npm install --save redux-thunk

index.js

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
// 根据 counter 函数创建 store 对象
const store = createStore(
	counter,
	applyMiddleware(thunk) // 应用上异步中间件
)

redux/actions.js

// 异步 action creator( 返回一个函数 )
export const incrementAsync = number => {
	return dispatch => {
		setTimeout(() => {
			dispatch(increment(number))
		}, 1000)
	}
}

components/counter.jsx

incrementAsync = () => {
	const number = this.refs.numSelect.value*1
	this.props.incrementAsync(number)
}

containers/app.jsx

import {increment, decrement, incrementAsync} from '../redux/actions'
// 向外暴露连接 App 组件的包装组件
export default connect(
	state => ({count: state}),
	{increment, decrement, incrementAsync}
)(Counter)

用 redux 调试工具
安装 chrome 浏览器插件:redux-devtools

下载工具依赖包
npm install --save-dev redux-devtools-extension

编码

import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(
	counter,
	composeWithDevTools(applyMiddleware(thunk))
)

纯函数和高阶函数

纯函数

  1. 一类特别的函数: 只要是同样的输入,必定得到同样的输出
  2. 必须遵守以下一些约束
    a. 不得改写参数
    b. 不能调用系统 I/O 的 API
    c. 能调用 Date.now()或者 Math.random()等不纯的方法
  3. reducer 函数必须是一个纯函数

高阶函数

  1. 理解: 一类特别的函数
    a. 情况 1: 参数是函数
    b. 情况 2: 返回是函数
  2. 常见的高阶函数:
    a. 定时器设置函数
    b. 数组的 map()/filter()/reduce()/find()/bind()
    c. react-redux 中的 connect 函数
  3. 作用:
    a. 能实现更加动态, 更加可扩展的功能

reducer为什么是纯函数?
从本质上讲,纯函数的定义如下:不修改函数的输入值,依赖于外部状态(比如数据库,DOM和全局变量),同时对于任何相同的输入有着相同的输出结果。

阅读源码可以看到,Redux接收一个给定的state(对象),然后通过循环将state的每一部分传递给每个对应的reducer。如果有发生任何改变,reducer将返回一个新的对象。如果不发生任何变化,reducer将返回旧的state。

Redux只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同(也就是Javascript对象浅比较)。如果你在reducer内部直接修改旧的state对象的属性值,那么新的state和旧的state将都指向同一个对象。因此Redux认为没有任何改变,返回的state将为旧的state。

深比较在真实的应用当中代价昂贵,因为通常js的对象都很大,同时需要比较的次数很多。

因此一个有效的解决方法是作出一个规定:无论何时发生变化时,开发者都要创建一个新的对象,然后将新对象传递出去。同时,当没有任何变化发生时,开发者发送回旧的对象。也就是说,新的对象代表新的state。 使用了新的策略之后,你能够比较两个对象通过使用!==比较两个对象的存储位置而不是比较两个对象的所有属性。

使用总结

需要引入的库:

redux
react-redux
redux-thunk
redux-devtools-extension(这个只在开发时需要)

redux文件夹:

action-types.js
actions.js
reducers.js
store.js

组件分2类:

ui组件(components): 不使用redux相关PAI
容器组件(containers): 使用redux相关API

传值通信

ReactDOM.render(
<App a={20} /> //相当于外面传来的值
)

在App组件中,可以用this.props获得a

props在react中单向,子元素不能更改父亲传入的props
如需更改,父组件要给子组件传入相关函数

有了redux,组件直接的通信就方便了,组件之间不用互相负责,只需对全局store负责
组件必须被connect装饰,才能得到redux中的值

redux的connect的原理:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

[mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用(例如,当 props 接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算)。
用了组件的context)

副作用

一个函数或表达式改变了外部的状态
redux有两个库能够处理副作用:thunk和saga

saga

在这里插入图片描述
saga概念来自Hector(os作者)写的论文
saga:不停止主进程,做一个易于插拔的额外进程,能够监控,影响主进程的分支进程
就是拦截机制
npm install --save redux-saga
中间件:state改变前 ->通过中间件 ->state改变后

dva

dva:彻底简化了react+redux+saga的书写
入口:
model文件像极了vuex

发布了151 篇原创文章 · 获赞 5 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览