Redux
什么是Redux
Redux是JavaScript状态容器,它的诞生是为了给 React 应用提供可预测化的状态管理机制。如果是一个数据状态非常复杂的应用,更多的时候发现React根本无法让两个组件互相交流,使用对方的数据,react的通过层级传递数据的这种方法是非常难受的,这个时候,迫切需要一个机制,把所有的state集中到组件顶部,能够灵活的将所有state各取所需的分发给所有的组件,这就是Redux。
如下图是不使用Redux和使用Redux时,父子组件之间的通信方式;
如果不用Redux,两个组件之间需要通信的话,可能需要多个中间组件为他们进行消息传递,这样浪费了资源,代码也复杂不易管理维护,而我们要传递state是非常麻烦的,而在Redux中,可以先把数据放在数据仓库中(store-公用状态存储空间),在这里就可以统一管理状态,如果哪个组件用到, 就去store中查找。所有的组件都可以通过action修改store,在store中获取最新状态,然而使用redux就可以很好的去解决组件之间的通信问题。
什么时候需要redux
简单来说如果UI层非常简单,没有很多互动,Redux就没必要,如果用了,反而会增加复杂性。以下情况可以用到:
(1)用户的使用方式复杂;
(2)不同身份的用户有不同的使用方式(比如用户和管理员);
(3)多个用户之间可以协作;
(4)与服务器大量交互,或者使用WebSocket。
从组件方面来看,以下也可以考虑使用Redux:
(1)某个组件的状态,需要共享;
(2)某个状态需要在任何地方都可以拿到;
(3)一个组件需要改变全局状态;
(4)一个组件需要改变另一个组件的状态。
redux有三大准则
1、单一数据源:整个应用的state存储再一个js对象中。
2、只读状态:唯一可以修改状态的方式,就是发送(dispatch)一个动作(Action)。
3、用纯函数去修改状态:为了实现根据action修改state值,我们就需要编写 Reducer,reducer本身是一个纯函数,接收state与action作为参数并返回新的state,纯函数好处在于输入确定,输出也会确定。
以下图来说一下他们之间的关系
Redux会将整个应用状态(数据)存储到Store中,这个store里面保存一棵状态树(state tree),组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件,组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新,其它组件可以通过订阅store中的状态(state)来刷新自己的视图。
怎么使用Redux
创建Redux的仓库 store和reducer
redux的工作流程有四个部分,其中最重要的是store这个部分,因为它把所有的数据都放到了store中进行管理,在敲代码的时候,要优先写store。
配置Redux开发环境最快的方法就是安装脚手架工具,需要安装
npm install -g create-react-app
用脚手架创建项目
create-react-app xxx
//启动
npm start
创建store仓库
先安装一个redux-react-
npm install --save redux 先安装
安装好redux,在src目录下创建一个store文件夹,然后创建一个index.js和reducer.js,编写代码。
//index.js
import { createStore } from 'redux' // 引入createStore方法
import reducer from './reducer'
const store = createStore() // 创建数据存储仓库
export default store
export default (state = defaultState,action)=>{
console.log(state,action)
return state
}
其中上面有两个参数:
state:指的是原始仓库的状态。
action:指的是action新传递的状态。
reducer:根据传入的action向store返回新的state
Redux工作流程
1、action:
这个方法用来触发 reducers 里面的处理逻辑。它是一个包含type和data的对象,而action的作用就是告诉状态管理员需要进行怎样的操作,可以理解为指令,需要发出多少动作就有多少指令。
const add =()=>{
return {
type:"add",//定义action类型
data:id,
}
}
2、store:
用来管理整个应用的状态,store可以理解为一个存储数据的仓库,用来管理state的单一对象。以下为Redux通过createStore整个函数,来生成store对象:
import { createStore } from 'redux';
var store = createStore(fn);
//store.getState():获取state ,经过 reducer 返回了一个新的 state,可以用该函数获取。
var state = store.getState();
//store.dispatch(action):发出 action,用于触发 reducer 更新 state,
store.dispatch({
type:'add',
data:id,
})
3、reducer:
reducer是一个函数,当dispatch之后,getState的状态发生了变化,而Reducer就是用来修改状态的,它接受action和当前state作为参数,根据不同的action做出不同的操作并返回一个新的state,即:(state,action)=>state
const reducer = function (state, action) {
//dispatch
return new_state;
//reducer.js
//相当于给store里增加了两个数据。
const defaultState = {
inputValue: '',
list: []
}
//reducer 可以接受state但是不能修改state
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState
}
if (action.type === 'add_input_value') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue) //push新的内容到列表中去
newState.inputValue = '';
return newState;
}
return state;
}
todoList.js
//TodoList.js
import React, { Component } from 'react';
import store from './store';
class TodoList extends Component {
constructor(props){
super(props)
console.log(store);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleList = this.handleList.bind(this);
this.btnClick = this.btnClick.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
store.subscribe(this.handleStoreChange)
}
handleStoreChange(){
this.setState(store.getState());
}
handleInputChange(e){
const action = {
type: 'change_input_value',
value: e.target.value
}
store.dispatch(action);
}
handleList(){
return this.state.list.map((item) => {
return <li>{item}</li>
})
}
btnClick(){
const action = {
type: 'add_input_value',
value: this.state.inputValue
}
store.dispatch(action);
}
render() {
return (
<div>
<div>
<input
value = {this.state.inputValue}
onChange = {this.handleInputChange}
/>
<button onClick={this.btnClick}>提交</button>
</div>
<ul>
{this.handleList()}
</ul>
</div>
);
}
}
export default TodoList;
以上中可以总结几点:
(1)store必须是唯一的,多个store是不允许的,就只能有一个store文件;
(2)只有store能改变自己的内容,Reducer不能改变;
(3)Reducer必须是纯函数。
Redux可将UI和业务逻辑进行拆分写
新建一个组件,把JSX部分和需要引入相关的类库也都复制过来,进行引入;剩下的就需要改造父组件进行传递值。
引入之后的redner函数可以写成:
render(){
return(
<TodoListWeb
inputValue={this.state.inputValue}
change_input_value={this.change_input_value}
btnClick={this.btnClick}
/ >
);
}
无状态组件
无状态组件就是一个函数,它不用继承任何的类,也不存在state;把UI组件改成无状态组件可以提高程序性能。
步骤如下:
1、不需要引入React的Component;
2、在我们的UI组件中,里面只返回JSX部分就行;
3、函数传递一个props参数,然后修改里面所有的props,去掉this;
import React from react;
const TodoListUi = function(props){
return(
<div> </div>
)
}
redux中间件原理及实现
如果提到中间件,可能会想到Express和Koa等服务端框架,但是中间件是干嘛的?中间件通常是运行再收到请求到处理请求之间,可能是实现日志记录这些预处理操作,在Redux中,中间件是运行action发送出去,到达reducer之间的一段代码。
1、先配置Redux-thunk组件
npm i --save redux-thunk
2、引入applyMiddleware
//引入之后按把thunk放到createStore的第二个参数就行。
const store = createStore(
reducer,
applyMiddleware(thunk)
) // 创建数据存储仓库
//如果你要使用中间件,就必须再redux引入applyMiddleware
//compose是使用增强函数
import { createStore , applyMiddleware ,compose } from 'redux' // 引入createStore方法
import reducer from './reducer'
import thunk from 'redux-thunk'
//利用compose创造一个增强函数,相当于建立了一个链式函数
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
//有了增强函数,把thunk加入进来,这样两个函数都可以执行
const enhancer = composeEnhancers(applyMiddleware(thunk))
//直接在createStore函数中的第二个参数,使用enhancer变量,相当于两个函数都执行了
const store = createStore( reducer, enhancer) // 创建数据存储仓库
export default store
)
从最里面看,这里的thunk就是满足redux的中间件实现;如果要使用中间件,就必须在redux中引入applyMiddleware;因为applyMiddleware函数可以接收多个中间件。它可返回一个高阶函数,而这个函数中会初始化store和重写dispatch逻辑,以便后续使用。
componentDidMount(){
const action = getTodoList()
store.dispatch(action)
}
现在我们执行这个方法,去修改todoList文件中的componentDidMount代码。
它还有一个中间件叫saga。
React-Redux介绍&安装
React-Redux这是一个React生态中常用组件,它可以简化Redux流程。
安装操作
没安装脚手架工具的跟上面Redux一样,react-react-app
然后安装react-redux
npm install --save react-redux
React-redux将组件分为容器组件和UI组件
1、前者会处理逻辑;
2、后者只负责显示和交互,内部不处理逻辑,状态完全由外部掌控。
两个核心
Provider
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
const App=(
<Provider store={store}>//包裹的store是提供者的一个属性
<TodoList />
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
这个Provider可以叫做顶层组件,一般我们都将顶层组件包裹在Provider组件中,所有组件就可以在react-redux的控制之下了。这个组件的目的是让所有组件能够访问到Redux中的数据。只要使用了这个组件,组件里的其它所有组件都可以使用store,这也是react-redux的核心组件。但是store必须作为参数放到Provider组件中。
connect
如何简单的去获取store中的数据呢?下面代码中再在TodoList.js文件中。引入connect,它是一个连接器,其实也是一个方法,有了它就可以获取数据。
先引入
//connect连接器在todoList里面写
import React, { Component } from 'react';
import store from './store'
import {connect} from 'react-redux'
class TodoList extends Component {
constructor(props){
super(props)
this.state = store.getState()
}
render() {
return (
<div>
<div>
<input value={this.props.inputValue}
onChange={this.props.inputChange} />
<button>提交</button>
</div>
<ul>
<li>内容</li>
</ul>
</div>
);
}
}
//映射关系就是把原来的state映射成组件中的props属性。
const stateToProps = (state)=>{
return {
inputValue : state.inputValue
}
}
const dispatchToProps = (dispatch) =>{
return {
inputChange(e){
console.log(e.target.value)
}
}
}
export default connect(stateToProps,dispatchToProps)(TodoList);
//stateToProps代表一个映射关系
其实也就是把Redux中的数据映射到React中的props中去。
映射关系做好,剩下的只要进行action的派发和reducer对业务逻辑的编写就可以了。流程基本上都是一样的。
//action
const dispatchToProps = (dispatch) =>{
return {
inputChange(e){
let action = {
type:'change_input',
value:e.target.value
}
dispatch(action)
}
}
}
//reducer
const defalutState = {
inputValue : 'neirong',
list :[]
}
export default (state = defalutState,action) =>{
if(action.type === 'change_input'){
let newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
return state
}
connect的作用就是把UI组件和业务逻辑代码分开,然后通过connect再连接到一块,让代码更加清晰和容易维护,这也是React-Redux的优点。