actionCreators是redux运行的第一个角色,他将用户将要操作的内容包装成一个对象,{key:value},包装成一个{type:data},type为操作的类型,而data是操作具体的数值
store为redux最为核心的角色,虽然他接受actionCreators创造的对象,但实质并不是store去处理,而是store交给了他手下的reducers去处理,reducer处理完交给store保存和更新状态
reducer是redux整个工作中,处理状态的工作者,状态的处理最后都是交给了reducer去处理
在一个react-cli项目中,打开终端,输入
npm i redux
注意:redux并不是react官方创作,只不过redux和react配合更加出众,甚至在Vue以及Angular都可以用redux去管理状态,但在Vue中Vue开发者团队打造了一个更适合Vue的Vuex
redux在底层采用了分别暴露的方式
import {createStore} from "redux"
export default createStore("这里要放入reducer")
import {INCREMENT, DECREMENT} from "../constant"
const initPre = 0
export default function countReducer(preState = initPre, action) {
const {type, data} = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
reducer函数可以接受两个参数,第一个参数是原状态,第二个参数是action对象,注意在一开始reducer可以初始化,在es6中可以在定义形参时传入默认值,在reducer初始化时是需要原状态,这里定义了一个变量initPre,用来指定state的初始值为0
在上述代码基础上我们就创建了一个最简版的redux,但redux状态的改变并不能引起页面的变化,那是因为react没有监视redux状态的变化,所以我们要在index.js中加入redux的监听
store.subscribe(() => {
root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
)
})
这样redux的状态修改就可以引起react发生重新渲染
再用react-redux之前我们要完善actionCreators
上述说过actionCreators的功能是创建action对象,我们之前可以手动的创建action对象,但actionCreators可以完成
import {INCREMENT, DECREMENT} from "../constant"
const createIncrementAction = data => ({type: INCREMENT, data})
const createDecrementAction = data => ({type: DECREMENT, data})
const createIncrementAsyncAction = (data, time) => dispatch => {setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
export {createDecrementAction, createIncrementAction, createIncrementAsyncAction}
redux不仅为我们同步生成action的方法,还提供了异步action,比如像ajax,计时器这样的副作用我们可以运用异步action
const createIncrementAsyncAction = (data, time) => dispatch => {setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
在redux中我们是不能让action成为一个函数,但在thunk中间件的基础上我们就可以redux支持异步函数式的action
npm i thunk
import {createStore, applyMiddleware} from "redux"
import thunk from "redux-thunk"
export default createStore(countReducer,applyMiddleware(thunk))
在这时redux就可以支持异步action
容器组件时redux和reactUI组件的中间桥梁,容器组件拿到redux的状态然后通过props的方法传给reactUI组件使用,如果UI组件要修改状态,同样也要通过容器组件,再由容器组件交给redux处理
import React, {Component, Fragment} from "react"
import {connect} from "react-redux"
import {createDecrementAction, createIncrementAction, createIncrementAsyncAction} from "../../redux/action/count"
class Count extends Component {
increment = () => {
const {value} = this.selectNumber
this.props.increment(parseInt(value))
}
decrement = () => {
const {value} = this.selectNumber
this.props.decrement(parseInt(value))
}
incrementIfOdd = () => {
if (this.props.count % 2 !== 0) {
const {value} = this.selectNumber
this.props.increment(parseInt(value))
}
}
incrementAsync = () => {
const {value} = this.selectNumber
this.props.incrementAsync(parseInt(value), 500)
}
render() {
return (
<Fragment>
<h1>Count组件,Person组件的人数为{this.props.person.length}</h1>
<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.incrementIfOdd}>当和为奇数时加</button>
<button onClick={this.incrementAsync}>异步加</button>
</Fragment>
)
}
}
const mapStateToProps = state => ({count: state.count, person: state.person})
const mapDispatchToProps = dispatch => ({
increment: number => dispatch(createIncrementAction(number)),
decrement: number => dispatch(createDecrementAction(number)),
incrementAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time))
})
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(Count)
export default CountContainer
react-redux为我们提供这个函数,来构建容器组件,从而构建桥梁,connect运用了函数的柯理化技术,可以为UI组件传递redux的状态以及操作状态的方法
mapStateToProps作为调用柯理化函数的第一个参数,该方法的作用是为UI组件提供redux的状态
mapDispatchToProps作为调用柯理化函数的第二个参数,该方法为UI组件提供操作状态的方法
我们可以用react-redux给我们提供的一个标签,为后代的UI组件自动分配store,来到index.js中
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from "./redux/store";
import {Provider} from "react-redux"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
);
reportWebVitals();
在之前我们要手动添加针对redux的状态的监听,但引入react-redux之后我们不需要再添加,因为react-redux为我们在底层做了处理
我们可以在浏览器的扩展里下载redux开发者工具,从而实现更好的开发体验
安装redux-devtools-extension
npm i redux-devtools-extension
import {createStore, applyMiddleware, combineReducers} from "redux"
import countReducer from "./reducer/count"
import personReducer from "./reducer/person";
import {composeWithDevTools} from "redux-devtools-extension"
import thunk from "redux-thunk"
const allReducer = combineReducers({count: countReducer, person: personReducer})
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
在创建store时我们不做特殊处理的化我们只能引用一个reducer
这时我们就需要一个api—combineReducers进行整合
import {createStore, applyMiddleware, combineReducers} from "redux"
import countReducer from "./reducer/count"
import personReducer from "./reducer/person";
import {composeWithDevTools} from "redux-devtools-extension"
import thunk from "redux-thunk"
const allReducer = combineReducers({count: countReducer, person: personReducer})
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
import React, {Component, Fragment} from "react"
import {connect} from "react-redux"
import {createDecrementAction, createIncrementAction, createIncrementAsyncAction} from "../../redux/action/count"
class Count extends Component {
increment = () => {
const {value} = this.selectNumber
this.props.increment(parseInt(value))
}
decrement = () => {
const {value} = this.selectNumber
this.props.decrement(parseInt(value))
}
incrementIfOdd = () => {
if (this.props.count % 2 !== 0) {
const {value} = this.selectNumber
this.props.increment(parseInt(value))
}
}
incrementAsync = () => {
const {value} = this.selectNumber
this.props.incrementAsync(parseInt(value), 500)
}
render() {
return (
<Fragment>
<h1>Count组件,Person组件的人数为{this.props.person.length}</h1>
<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.incrementIfOdd}>当和为奇数时加</button>
<button onClick={this.incrementAsync}>异步加</button>
</Fragment>
)
}
}
const mapStateToProps = state => ({count: state.count, person: state.person})
const mapDispatchToProps = dispatch => ({
increment: number => dispatch(createIncrementAction(number)),
decrement: number => dispatch(createDecrementAction(number)),
incrementAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time))
})
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(Count)
export default CountContainer
import React, {Component, Fragment, PureComponent} from "react"
import {nanoid} from "nanoid"
import {connect} from "react-redux"
import {createAddPersonAction} from "../../redux/action/person"
class Person extends Component {
addPersons = () => {
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 (
<Fragment>
<h1>Person组件,Count组件的和为{this.props.count}</h1>
<input placeholder="输入名字" type="text" ref={c => this.nameNode = c}/>
<input placeholder="输入年龄" type="text" ref={c => this.ageNode = c}/>
<button onClick={this.addPersons}>添加</button>
<Length per={this.props.person}/>
<ul>
{
this.props.person.map(p => {
return <li key={p.id}>{p.name}----{p.age}</li>
})
}
</ul>
</Fragment>
)
}
}
class Length extends PureComponent {
render() {
return (
<Fragment>
<h2>人数为:{this.props.per.length}</h2>
</Fragment>
)
}
}
export default connect(
state => ({person: state.person, count: state.count}),
{
addPerson: createAddPersonAction
}
)(Person)
import {INCREMENT, DECREMENT} from "../constant"
const createIncrementAction = data => ({type: INCREMENT, data})
const createDecrementAction = data => ({type: DECREMENT, data})
const createIncrementAsyncAction = (data, time) => dispatch => {setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
export {createDecrementAction, createIncrementAction, createIncrementAsyncAction}
import {ADDPERSON} from "../constant"
export const createAddPersonAction = (personObj) => ({type: ADDPERSON, data: personObj})
import {INCREMENT, DECREMENT} from "../constant"
const initPre = 0
export default function countReducer(preState = initPre, action) {
const {type, data} = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
import {ADDPERSON} from "../constant"
const initState = []
export default function personReducer(preState = initState, action) {
const {type, data} = action
switch (type) {
case ADDPERSON:
return [...preState, data]
default:
return preState
}
}
import {createStore, applyMiddleware, combineReducers} from "redux"
import countReducer from "./reducer/count"
import personReducer from "./reducer/person";
import {composeWithDevTools} from "redux-devtools-extension"
import thunk from "redux-thunk"
const allReducer = combineReducers({count: countReducer, person: personReducer})
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
const INCREMENT = "increment"
const DECREMENT = "decrement"
const ADDPERSON = "addPerson"
export {INCREMENT, DECREMENT, ADDPERSON}
import {Fragment} from "react"
import './App.css';
import Count from "./containers/Count";
import Person from "./containers/Person";
function App() {
return (
<Fragment>
<Count/>
<br/>
<Person/>
</Fragment>
);
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from "./redux/store";
import {Provider} from "react-redux"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>
);
reportWebVitals();