redux是什么
- redux是一个用于做
状态管理 (即数据管理)的JS 库。 注意:它是JS库不是react的插件库,vue和Angular有都可以使用redux(只是redux和react结合使用的情况比较多,vue有自己专门的状态管理库——vuex - 作用:集中式管理react应用中
多个组件 的共享状态(数据)
redux使用情况
- 共享:当一个组件中的数据其他组件都需要使用的时候可以使用redux。redux将公共数据进行存储,哪个组件需要使用,直接获取即可。
- 通信:一个组件需要改变另一个组件的状态的时候
redux工作原理
redux安装
npm i redux
redux的3个核心概念
Action——形成动作
包含2个属性
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值类型任意,可选属性
例子: { type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
Reducers——操作状态
用于初始化状态、加工状态。
- 加工时,根据旧的state和action,产生新的state数据
- 初始化时,旧的state为undefined,action中的type为
@@redux/INITk.s.p.9.6.9 k.s.p.9.6.9 是随机值 (Reducers的初始化是store自动触发的)
注意: Reducers一般放的都是最基础的操作,不带任何逻辑判断的操作。
store——存储状态
将state、action、reducer联系在一起的对象,使整个redux得以运行
API
store.getState() 获取redux中存储的数据store.dispatch({type:'increment',data:value}) 更新redux中的数据store.subscribe(()=>{}) 检测redux中存储的状态是否发生改变,只要redux中所保存的任意一个状态发生改变,都会调用该回调
redux的响应式
注意: react的state中的属性如果使用setState() 方法进行更新,react就会重新调用render方法进行页面渲染,实现state中的数据一更新页面中的数据就更新的效果。 但是redux只是一个数据存储库,里面的数更新后react不会重新渲染页面,这就需要我们利用store.subscribe(()=>{}) 和this.setState({}) 手动重新渲染。
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
如果组件过多每个组件都需要添加该段代码,可能有些麻烦。可以直接在index.jsx中直接监听,一旦redux中的状态发生变化就进行整个项目的重新渲染: index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App"
import store from "./redux/store"
ReactDOM.render(<App/>, document.getElementById("root"))
store.subscribe(()=>{
ReactDOM.render(<App/>, document.getElementById("root"))
})
redux实现加减案例
效果:
不使用ActionCreators实现
代码: components/Count/index.jsx
import React, { Component } from 'react'
import store from "../../redux/store"
export default class Count extends Component {
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
increment=()=>{
const {value} = this.selectNumber
store.dispatch({type:'increment',data:value*1})
}
decrement=()=>{
const {value} = this.selectNumber
store.dispatch({type:'decrement',data:value*1})
}
oddIncrement=()=>{
const count = store.getState()
const {value} = this.selectNumber
if(count%2 !== 0) {
store.dispatch({type:'increment',data:value*1})
}
}
asyncIncrement=()=>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch({type:'increment',data:value*1})
},500)
}
render() {
return (
<div>
{}
<h2>当前求和为:{store.getState()}</h2>
<select ref={c => this.selectNumber = c}>
<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.oddIncrement}>当前求和为奇数再+</button>
<button onClick={this.asyncIncrement}>异步加</button>
</div>
)
}
}
redux/count_reducer.js
export default function countReducer(preState, action) {
if(preState === undefined) {
preState = 99
}
const {type, data} = action
switch(type) {
case 'increment':
return preState+data
case 'decrement':
return preState-data
default://初始化
return preState
}
}
redux/store.js
import {createStore} from "redux"
import countReducer from "./count_reducer"
export default createStore(countReducer)
App.jsx
import React, { Component } from 'react'
import Count from './components/Count'
export default class App extends Component {
render() {
return (
<div>App
<Count/>
</div>
)
}
}
index.jsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App"
ReactDOM.render(<App/>, document.getElementById("root"))
使用ActionCreators实现
在上面代码的基础上添加 count_action.js 文件, 并对Count/index.jsx文件做相应修改 redux/count_action.js
import {INCREMENT,DECREMENT} from './const'
export const createIncrementAction = data=>({type:'increment', data})
export const createDecrementAction = data=>({type:'decrement', data})
components/Count/index.jsx
import React, { Component } from 'react'
import store from "../../redux/store"
import { createIncrementAction, createDecrementAction } from '../../redux/count_action'
export default class Count extends Component {
increment=()=>{
const {value} = this.selectNumber
store.dispatch(createIncrementAction(value*1))
}
decrement=()=>{
const {value} = this.selectNumber
store.dispatch(createDecrementAction(value*1))
}
oddIncrement=()=>{
const count = store.getState()
const {value} = this.selectNumber
if(count%2 !== 0) {
store.dispatch(createIncrementAction(value*1))
}
}
asyncIncrement=()=>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch(createIncrementAction(value*1))
},500)
}
render() {
return (
<div>
{}
<h2>当前求和为:{store.getState()}</h2>
<select ref={c => this.selectNumber = c}>
<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.oddIncrement}>当前求和为奇数再+</button>
<button onClick={this.asyncIncrement}>异步加</button>
</div>
)
}
}
异步Action
ActionCreators可以创建两种Action对象:
- 同步action, 即action的值为
对象 形式 - 异步action, 即action的值为
函数function 形式(开启异步任务的function) 但是使用异步action必须引用一个中间件redux-thunk 进行处理. 安装 : npm i redux-thunk 在store.js中引用 :
import {createStore} from "redux"
import countReducer from "./count_reducer"
import {applyMiddleware} from "redux"
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))
异步action中一般都会调用同步action: eg:
import {INCREMENT,DECREMENT} from './const'
import store from './store'
export const createIncrementAction = data=>({type:INCREMENT, data})
export const createDecrementAction = data=>({type:DECREMENT, data})
export const createIncrementAsyncAction = (data, time)=>{
return ()=>{
setTimeout(()=>{
store.dispatch(createIncrementAction(data))
},time)
}
}
还可以简写为
import {INCREMENT,DECREMENT} from './const'
export const createIncrementAction = data=>({type:INCREMENT, data})
export const createDecrementAction = data=>({type:DECREMENT, data})
export const createIncrementAsyncAction = (data, time)=>{
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
react-redux
- 所有的
UI组件 都应该被一个容器组件 包裹,他们是父子关系。 容器组件是真正和redux打交道的 ,里面可以随意的使用redux的api。 UI组件中不能使用任何redux的api。- 容器组件会传给UI组件:
- redux中所保存的状态。
- 用于操作状态的方法。
并且均通过props 属性传递。 容器组件一般写在containers文件夹中,UI组件一般写在components文件夹中。
使用
容器组件创建
- 创建containers文件夹,创建容器组件文件如
containers/Count/index.jsx - 下载react-redux:
npm i react-redux - 在容器组件中,引入并使用react-redux
containers/Count/index.jsx
import CountUI from "../../components/Count";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction } from "../../redux/count_action";
import {connect} from 'react-redux'
function mapStateToProps(state){
return {count:state}
}
function mapDispatchToProps(dispatch){
return {
increment:data=>dispatch(createIncrementAction(data)),
decrement:data=>dispatch(createDecrementAction(data)),
incrementAsync:(data,time)=>dispatch(createIncrementAsyncAction(data, time)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
修改App.jsx在渲染容器组件而不是UI组件
App.jsx 渲染容器组件而不是UI组件,并将渲染容器所需要的store作为参数传递过去。
import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>App
<Count store={store}/>
</div>
)
}
}
UI组件接收容器组件传递过来的属性和方法做相应的修改
components/count/index.jsx
import React, { Component } from 'react'
export default class Count extends Component {
increment=()=>{
const {value} = this.selectNumber
this.props.increment(value*1)
}
decrement=()=>{
const {value} = this.selectNumber
this.props.decrement(value*1)
}
oddIncrement=()=>{
const {value} = this.selectNumber
if(this.props.count%2===1){
this.props.increment(value*1)
}
}
asyncIncrement=()=>{
const {value} = this.selectNumber
this.props.incrementAsync(value*1, 500)
}
render() {
return (
<div>
{}
<h2>当前求和为:{this.props.count}</h2>
<select ref={c => this.selectNumber = c}>
<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.oddIncrement}>当前求和为奇数再+</button>
<button onClick={this.asyncIncrement}>异步加</button>
</div>
)
}
}
优化一:容器组件的简写
containers/Count/index.jsx
import CountUI from "../../components/Count";
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction } from "../../redux/count_action";
import {connect} from 'react-redux'
export default connect(
state =>({count:state}),
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementAsyncAction,
}
)(CountUI)
优化二:优化store传参、优化检测渲染
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App"
import store from "./redux/store"
import {Provider} from "react-redux"
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById("root")
)
优化三:容器组件和UI组件的融合_最终版
删除components/Count/index.jsx文件
修改container/Count/index.jsx:
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction } from "../../redux/count_action";
import {connect} from 'react-redux'
import React, { Component } from 'react'
class Count extends Component {
increment=()=>{
const {value} = this.selectNumber
this.props.increment(value*1)
}
decrement=()=>{
const {value} = this.selectNumber
this.props.decrement(value*1)
}
oddIncrement=()=>{
const {value} = this.selectNumber
if(this.props.count%2===1){
this.props.increment(value*1)
}
}
asyncIncrement=()=>{
const {value} = this.selectNumber
this.props.incrementAsync(value*1, 500)
}
render() {
return (
<div>
{}
<h2>当前求和为:{this.props.count}</h2>
<select ref={c => this.selectNumber = c}>
<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.oddIncrement}>当前求和为奇数再+</button>
<button onClick={this.asyncIncrement}>异步加</button>
</div>
)
}
}
export default connect(
state =>({count:state}),
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementAsyncAction,
}
)(Count)
React-redux的使用总结
新建container/组件名/index.jsx文件,编写如下内容:
- 定义UI组件,不暴露
在UI组件中使用this.props.XXXXX 来读取状态和方法 - 定义容器组件,并进行暴露
利用connect生成一个容器组件
export default connect(
state =>({count:state}),
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementAsyncAction,
}
)(Count)
Redux的多组件共享——实现redux的意义
编写personUI组件
container/Person/index.jsx
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
export default class Person extends Component {
addPerson=()=>{
const name = this.nameNode.value
const age = this.ageNode.value
const personObj ={
id:nanoid(),
name,
age
}
}
render() {
return (
<div>
<hr/>
<h2>Person组件</h2>
<input ref={c=>this.nameNode=c} type="text" placeholder='请输入名字'></input>
<input ref={c=>this.ageNode=c} type="text" placeholder='请输入年龄'></input>
<button onClick={this.addPerson}>添加</button>
</div>
)
}
}
配置Person的redux
import { ADD_PERSON } from "../const";
export const createAddPersonAction =(personObj)=>({type:ADD_PERSON, data:personObj})
import {INCREMENT,DECREMENT} from '../const'
export default function countReducer(preState, action) {
if(preState === undefined) {
preState = 99
}
const {type, data} = action
switch(type) {
case INCREMENT:
return preState+data
case DECREMENT:
return preState-data
default://初始化
return preState
}
}
store引入
多个组件必须使用combineReducers 进行引入。
import {createStore, combineReducers} from "redux"
import countReducer from "./reducers/count"
import personReducer from "./reducers/count"
import {applyMiddleware} from "redux"
import thunk from 'redux-thunk'
const allReducer = combineReducers({
count: countReducer,
person: personReducer
})
export default createStore(allReducer, applyMiddleware(thunk))
编写personUI组件的容器化——实现addPerson
由于是多个组件,所以需要使用 state =>({person: state.person}), 形式进行redux数据的引入
containers/Person/index.jsx
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import { connect } from 'react-redux'
import {createAddPersonAction} from '../../redux/actions/person'
class Person extends Component {
addPerson=()=>{
const name = this.nameNode.value
const age = this.ageNode.value
const personObj ={
id:nanoid(),
name,
age
}
this.props.addPerson(personObj)
this.nameNode.value=""
this.ageNode.value=""
}
render() {
return (
<div>
<hr/>
<h2>Person组件</h2>
<input ref={c=>this.nameNode=c} type="text" placeholder='请输入名字'></input>
<input ref={c=>this.ageNode=c} type="text" placeholder='请输入年龄'></input>
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.person.map((person)=>{
return <li key={person.id}>{person.name}--{person.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
state =>({person: state.person}),
{addPerson: createAddPersonAction}
)(Person)
实现Person组件读取Count组件使用的store数据
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import { connect } from 'react-redux'
import {createAddPersonAction} from '../../redux/actions/person'
class Person extends Component {
addPerson=()=>{
const name = this.nameNode.value
const age = this.ageNode.value
const personObj ={
id:nanoid(),
name,
age
}
this.props.addPerson(personObj)
this.nameNode.value=""
this.ageNode.value=""
}
render() {
return (
<div>
<hr/>
<h2>Person组件</h2>
<input ref={c=>this.nameNode=c} type="text" placeholder='请输入名字'></input>
<input ref={c=>this.ageNode=c} type="text" placeholder='请输入年龄'></input>
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.person.map((person)=>{
return <li key={person.id}>{person.name}--{person.age}</li>
})
}
</ul>
<h2>上方组件的和:{this.props.count}</h2>
</div>
)
}
}
export default connect(
state =>({person: state.person,
count: state.count}),
{addPerson: createAddPersonAction}
)(Person)
效果:
目录结构
注意:reducers中返回结果的形式
reducers中返回的结果:redux会对返回值进行浅比较,如果返回结果和之前的一样就不会更新页面
reducers/person.js
import { ADD_PERSON } from "../const"
const initState=[{id:'001', name:"Tom", age:18}]
export default function personReducer(preState=initState, action){
const {type, data}= action
switch(type){
case ADD_PERSON:
return [data, ...preState]
default:
return preState
}
}
注意:纯函数
定义: 只要同样的输入必定得到同样的输出的函数叫做纯函数。 纯函数必须遵守如下约束:
- 不得改写参数数据
- 不会产生任何副作用:不可以发送网络请求、不可以使用输入输出设备
- 不能调用Data.now()、Math.random()等不纯的方法
redux的reducer必须是纯函数。
Redux的开发者工具
配置使用:
- 安装库:
npm i redux-devtools-extension - 引入:
store.js:
import {createStore, combineReducers} from "redux"
import countReducer from "./reducers/count"
import personReducer from "./reducers/person"
import {applyMiddleware} from "redux"
import thunk from 'redux-thunk'
import {composeWithDevTools} from "redux-devtools-extension"
const allReducer = combineReducers({
count: countReducer,
person: personReducer
})
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
功能解释:
|