Redux
简介
其实就是一个集中的状态管理技术, 类似于VueX, 以及后端的分布式配置中心, 在前端的文章里提后端,是不是不太好~, 但是能学习这个技术的人, 从简短的一句话中应该就已经简单的了解了这个技术,以及它的使用情况, 我就不过多写概念了, 主要写使用方式
Redux工作流程
三个核心概念
Action
- 动作对象
- 包含两个属性
字段 | 作用 | 数据类型 | 是否唯一 | 是否必填 |
---|---|---|---|---|
type | 标识属性 | 字符串 | 是 | 是 |
data | 数据属性 | 任意 | 否 | 否 |
{
"type":"add",
"data:":{
"name":"flower",
"age":18
}
}
Reducer
- 用于初始化状态, 加工状态
- 加工时, 依据旧的state和action,产生新的state的纯函数
Store
- 将state, action, reducer 联系在一起的对象
- 如何获取:
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
- 此对象的功能
函数 | 参数 | 作用 |
---|---|---|
getState() | 无 | 获取state |
dispatch(action) | action对象 | 分发action,处罚reducer调用,产生新的state |
subscribe(listener) | listener对象 | 注册监听,当产生新的state时,自动调用 |
添加依赖
代码语言:javascript复制yarn add redux
求和案例
React版本
代码语言:javascript复制import React, {Component} from 'react';
class Count extends Component {
state = {
count: 0
}
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>当前求和为:{this.state.count}</h1>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
let {count} = this.state
if (type === 'add') {
count = count value
this.setState({count})
return
}
if (type === 're') {
count = count - value
this.setState({count})
return
}
if (type === 'j') {
if (count % 2 !== 0) {
count = count value
this.setState({count})
}
return;
}
if (type === 'async') {
count = count value
setTimeout(() => {
this.setState({count})
}, 2000)
}
}
}
}
export default Count;
Redux精简版
创建store.js
代码语言:javascript复制/**
* 1: 引入createStore
* 2: 引入为自定义组件服务的reducer
* 3: 对外暴露store
*/
import {legacy_createStore as createStore} from 'redux'
import countReducer from './count_reducer'
export default createStore(countReducer)
创建count_reducer.js
代码语言:javascript复制/**
* 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数
* 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action)
*/
const initValue = 0
export default function countReducer(preState = initValue, action) {
const {type, data} = action
if (type === 'add')
return preState data
if (type === 're')
return preState - data
return preState
}
使用redux改造原有Count组件
代码语言:javascript复制import React, {Component} from 'react';
import store from "../../redux/count/store";
class Count extends Component {
componentDidMount() {
// 检测Redux中状态的变化, 只要变化, 就调用Render
store.subscribe(()=>{
// 调用刷新页面函数, 啥也不干就 重新渲染一下render
this.forceUpdate()
})
}
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
if (type === 'add') {
store.dispatch({type:'add',data:value})
}
if (type === 're') {
store.dispatch({type:'re',data:value})
}
if (type === 'j') {
if (store.getState() % 2 !== 0) {
store.dispatch({type:'add',data:value})
}
}
if (type === 'async') {
setTimeout(() => {
store.dispatch({type:'add',data:value})
}, 2000)
}
}
}
}
export default Count;
函数小结
函数 | 参数 | 作用 |
---|---|---|
getState() | 无 | 获取state |
dispatch(action) | action对象 | 分发action,处罚reducer调用,产生新的state |
subscribe(listener) | listener对象 | 注册监听,当产生新的state时,自动调用 |
Redux完整版
对面上的案例进行改造
新增常量constant.js
代码语言:javascript复制/**
* 该模块用于定义action对象的type类型, 统一管理常量值
*/
export const ADD = 'add';
export const RE = 're';
改造reducer, 引入常量
代码语言:javascript复制/**
* 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数
* 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action)
*/
import {ADD, RE} from "./constant";
const initValue = 0
export default function countReducer(preState = initValue, action) {
const {type, data} = action
console.log(preState, action)
if (type === ADD)
return preState data
if (type === RE)
return preState - data
return preState
}
新增count_action.js
代码语言:javascript复制/**
* 该文件专门为Count组件生成Action对象
*/
import {ADD, RE} from './constant'
export const creatAddAction = data => ({type: ADD, data})
export const creatReAction = data => ({type: RE, data})
使用action函数替换传入的对象
代码语言:javascript复制import React, {Component} from 'react';
import store from "../../redux/count/store";
import {creatAddAction, creatReAction} from "../../redux/count/count_action";
class Count extends Component {
componentDidMount() {
// 检测Redux中状态的变化, 只要变化, 就调用Render
store.subscribe(()=>{
// 调用刷新页面函数, 啥也不干就 重新渲染一下render
this.forceUpdate()
})
}
render() {
return (
<div style={{textAlign: 'center'}}>
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
if (type === 'add') {
store.dispatch(creatAddAction(value))
}
if (type === 're') {
store.dispatch(creatReAction(value))
}
if (type === 'j') {
if (store.getState() % 2 !== 0) {
store.dispatch(creatAddAction(value))
}
}
if (type === 'async') {
setTimeout(() => {
store.dispatch(creatAddAction(value))
}, 2000)
}
}
}
}
export default Count;
异步Action
在调用dispatch的时候传入的action对象, 如果对象是Object, 那么就是同步的action, 如果是函数, 那么就是异步的action
添加依赖
代码语言:javascript复制yarn add redux-thunk
编写异步函数
代码语言:javascript复制/**
* 该文件专门为Count组件生成Action对象
*/
import {ADD, RE} from './constant'
// 同步Action, 返回值为Object
export const creatAddAction = data => ({type: ADD, data})
export const creatReAction = data => ({type: RE, data})
// 所谓的异步Action,就是指所谓的返回值是函数
export const creatAsyncAddAction = (data,timeout) => dispatch => setTimeout(()=> dispatch(creatAddAction(data)) ,timeout)
设置Store支持函数
代码语言:javascript复制/**
* 1: 引入createStore
* 2: 引入为自定义组件服务的reducer
* 3: 对外暴露store
*/
import {legacy_createStore as createStore, applyMiddleware} from 'redux'
import countReducer from './count_reducer'
// 用于支持异步Action
import thunk from "redux-thunk";
export default createStore(countReducer,applyMiddleware(thunk))
修改Count组件
代码语言:javascript复制if (type === 'async') {
// setTimeout(() => {
store.dispatch(creatAsyncAddAction(value, 500))
// }, 2000)
}
去除setTimeout, 交由Store支持
React-Redux
简介
一看名称就是react自己写的, 应该是封装了redux,方便使用集成
工作流程
其实就是在Count组件外面包了一层用于和Redux做交互的容器, 用于获取数据和交互
添加依赖
代码语言:javascript复制yarn add react-redux
使用react-redux实现求和案例
修改Count组件
代码语言:javascript复制import React, {Component} from 'react';
class Count extends Component {
render() {
const {count} = this.props
return (
<div style={{textAlign: 'center'}}>
<h1>当前求和为:{count}</h1>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
const {count, add, re, addAsync} = this.props
if (type === 'add') {
add(value)
}
if (type === 're') {
re(value)
}
if (type === 'j') {
if (count % 2 !== 0) {
add(value)
}
}
if (type === 'async') {
addAsync(value, 500)
}
}
}
}
export default Count;
定义容器组件
代码语言:javascript复制// 引入Count组件
import CountUI from "../../components/Count";
// 引入connect 用于连接UI和store
import {connect} from 'react-redux'
import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action";
// 函数的返回值作为状态传递给了UI组件
const mapStateToProps = (state) => {
return {
count: state
}
}
// 函数的返回值作为函数操作传递给了UI组件
const mapDispatchToProps = (dispatch) => {
return {
add: (data) => {
dispatch(creatAddAction(data))
},
addAsync: (data,timeout) => {
dispatch(creatAsyncAddAction(data,timeout))
},
re: (data) => {
dispatch(creatReAction(data))
}
}
}
// 创建并暴露一个Count的容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
修改APP组件
代码语言:javascript复制import React, {Component} from 'react';
import Count from "./containers/Count";
import store from "./redux/count/store";
class App extends Component {
render() {
return (
<div>
{/* 传入store对象 */}
<Count store={store}/>
</div>
);
}
}
export default App;
修改index组件
代码语言:javascript复制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/count/store";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
// 订阅store状态更新
store.subscribe(()=>{
root.render(<App/>)
})
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
优化求和案例
优化容器组件
代码语言:javascript复制// 引入Count组件
import CountUI from "../../components/Count";
// 引入connect 用于连接UI和store
import {connect} from 'react-redux'
import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action";
// 创建并暴露一个Count的容器组件
export default connect(
state => ({
count: state
}),
{
add: creatAddAction,
addAsync: creatAsyncAddAction,
re: creatReAction
}
)(CountUI);
原理
原来的是一个函数, 优化为一个对象, 直接返回一个action, 然后react-redux会自动调用dispatch进行action分发
优化Index组件
代码语言:javascript复制import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
可以将原来添加的监听删除了, 因为react-redux会自动监听redux的状态变化, 并重新渲染render
优化Store传入
将原有的APP组件中传入的store删除
代码语言:javascript复制import React, {Component} from 'react';
import Count from "./containers/Count";
class App extends Component {
render() {
return (
<div>
{/* 传入store对象 */}
<Count/>
</div>
);
}
}
export default App;
修改Index组件
代码语言:javascript复制import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 引入store 提供者
import store from "./redux/count/store";
import {Provider} from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
这样就可以将store传递给所有需要store的容器组件了
将UI组件和容器组件整合
代码语言:javascript复制import React, {Component} from 'react';
// 引入connect 用于连接UI和store
import {connect} from 'react-redux'
import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action";
class Count extends Component {
render() {
const {count} = this.props
return (
<div style={{textAlign: 'center'}}>
<h1>当前求和为:{count}</h1>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
const {count, add, re, addAsync} = this.props
if (type === 'add') {
add(value)
}
if (type === 're') {
re(value)
}
if (type === 'j') {
if (count % 2 !== 0) {
add(value)
}
}
if (type === 'async') {
addAsync(value, 500)
}
}
}
}
// 创建并暴露一个Count的容器组件
export default connect(
state => ({
count: state
}),
{
add: creatAddAction,
addAsync: creatAsyncAddAction,
re: creatReAction
}
)(Count);
这样就不用增加工作量了, 每次写容器都得写两个文件了
多组件状态管理 数据交互
上面一直在用一个Count组件玩, 并没有涉及到组件交互和多组件状态存储, 下面就来玩一下
完整案例
Index组件
代码语言:javascript复制import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
// 引入store 提供者
import store from "./redux/count/store";
import {Provider} from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
reportWebVitals();
App组件
代码语言:javascript复制import React, {Component} from 'react';
import Count from "./containers/Count";
import Person from "./containers/Person";
class App extends Component {
render() {
return (
<div>
{/* 传入store对象 */}
<Count/>
<hr/>
<Person />
</div>
);
}
}
export default App;
Count容器组件 UI组件
代码语言:javascript复制import React, {Component} from 'react';
// 引入connect 用于连接UI和store
import {connect} from 'react-redux'
import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action";
class Count extends Component {
render() {
const {count,persons} = this.props
return (
<div style={{textAlign: 'center'}}>
<h2>Count组件</h2>
<h3>下方Person组件的值为</h3>
<ul>
<li>姓名-年龄</li>
{
persons.map(person => {
return <li key={person.id}>{person.name "-" person.age}</li>
})
}
</ul>
<h4>当前求和为:{count}</h4>
<select ref={c => this.count = c} style={{width: '50px'}}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button style={{marginLeft: '10px'}} onClick={this.sum('add')}> </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button>
<button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数 </button>
<button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步 </button>
</div>
);
}
sum = type => {
return event => {
let {value} = this.count
value = parseInt(value)
const {count, add, re, addAsync} = this.props
if (type === 'add') {
add(value)
}
if (type === 're') {
re(value)
}
if (type === 'j') {
if (count % 2 !== 0) {
add(value)
}
}
if (type === 'async') {
addAsync(value, 500)
}
}
}
}
// 创建并暴露一个Count的容器组件
export default connect(
state => ({
count: state.count,
persons: state.persons
}),
{
add: creatAddAction,
addAsync: creatAsyncAddAction,
re: creatReAction
}
)(Count);
Person容器组件 UI组件
代码语言:javascript复制import React, {Component} from 'react';
import {connect} from "react-redux";
import {createAddPersonAction} from "../../redux/count/person_action";
import {nanoid} from "nanoid";
class Person extends Component {
render() {
const {persons,count} = this.props
console.log(persons);
return (
<div style={{textAlign: 'center'}}>
<h2>Person组件</h2>
<h3>Count组件求和为:{count}</h3>
<input ref={c => this.name = c} type="text" placeholder={"请输入姓名"}/>
<input ref={c => this.age = c} type="text" placeholder={"请输入年龄"}/>
<button onClick={this.addPerson}>添加</button>
<ul>
<li>姓名-年龄</li>
{
persons.map(person => {
return <li key={person.id}>{person.name "-" person.age}</li>
})
}
</ul>
</div>
);
}
addPerson = () => {
const name = this.name.value
const age = this.age.value
const {addPerson} = this.props
const person = {
id:nanoid(),
name,
age
}
console.log(person);
addPerson(person)
}
}
export default connect(state=>({
persons:state.persons,
count:state.count
}),{
addPerson: createAddPersonAction
})(Person)
常量JS
代码语言:javascript复制/**
* 该模块用于定义action对象的type类型, 统一管理常量值
*/
export const ADD = 'add';
export const RE = 're';
export const ADD_PERSON = 'add_person';
StoreJS
代码语言:javascript复制/**
* 1: 引入createStore
* 2: 引入为自定义组件服务的reducer
* 3: 对外暴露store
*/
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
import countReducer from './count_reducer'
import personReducer from './person_reducer'
// 用于支持异步Action
import thunk from "redux-thunk";
// 使用combineReducers合并多个Reducer
// 字段:reducer
const allReducers = combineReducers({
count:countReducer,
persons:personReducer
})
export default createStore(allReducers,applyMiddleware(thunk))
Count Create Action
代码语言:javascript复制/**
* 该文件专门为Count组件生成Action对象
*/
import {ADD, RE} from './constant'
// 同步Action, 返回值为Object
export const creatAddAction = data => ({type: ADD, data})
export const creatReAction = data => ({type: RE, data})
// 所谓的异步Action,就是指所谓的返回值是函数
export const creatAsyncAddAction = (data,timeout) => dispatch => setTimeout(()=> dispatch(creatAddAction(data)) ,timeout)
Person Create Action
代码语言:javascript复制import {ADD_PERSON} from "./constant";
export const createAddPersonAction = person => ({type:ADD_PERSON,data:person})
Count Reducer
代码语言:javascript复制/**
* 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数
* 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action)
*/
import {ADD, RE} from "./constant";
const initValue = 0
export default function countReducer(preState = initValue, action) {
const {type, data} = action
console.log(preState, action)
if (type === ADD)
return preState data
if (type === RE)
return preState - data
return preState
}
Person Reducer
代码语言:javascript复制import {ADD_PERSON} from "./constant";
const initValue = []
export default function personHandler(preState = initValue, action) {
const {type, data} = action
if (type === ADD_PERSON) {
return [data, ...preState]
}
return preState
}
效果
实现了多组件Store存储,并交互数据
纯函数
- 一些特别的函数,只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下的约束
- 不得改写参数数据
- 不会产生任何副作用, 例如网络请求, 输入和输出设备
- 不能调用Date.now()或者Math,random等不纯的方法
- redux的reducer函数必须是一个纯函数
高阶函数
- 理解: 一类特别的函数
- 情况1: 参数是函数
- 情况2: 返回是函数
- 常见的高阶函数:
- 定时器设置函数
- 数组的forEach()/map()/filter()/reduce()/find()/bind()
- promise
- react-redux中的connect函数
- 作用: 能实现更加动态, 更加可扩展的功能
Redux开发者工具
应为我也不能上Google只能粘贴一个文件夹了
添加依赖
代码语言:javascript复制yarn add redux-devtools-extension
修改StoreJs
代码语言:javascript复制/**
* 1: 引入createStore
* 2: 引入为自定义组件服务的reducer
* 3: 对外暴露store
*/
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
import countReducer from './count_reducer'
import personReducer from './person_reducer'
// 用于支持异步Action
import thunk from "redux-thunk";
// 引入Redux开发者工具
import {composeWithDevTools} from 'redux-devtools-extension'
// 使用combineReducers合并多个Reducer
// 字段:reducer
const allReducers = combineReducers({
count:countReducer,
persons:personReducer
})
// 调用
export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)))
效果
可以记录组件的action执行和值的变化
React项目打包部署
代码语言:javascript复制yarn build
代码语言:javascript复制E:jsreact_redux>yarn build
yarn run v1.22.19
$ react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
53.7 kB buildstaticjsmain.555f55e7.js
1.79 kB buildstaticjs787.a95b438b.chunk.js
264 B buildstaticcssmain.e6c13ad2.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
yarn global add serve
serve -s build
Find out more about deployment here:
https://cra.link/deployment
Done in 11.82s.
E:jsreact_redux>
打包完成后会生成一个build文件夹, 我记得Vue应该是dist
代码语言:javascript复制npm -i serve -g
全局安装serve 当然, 真的上线也不是这么玩的, 一般前端上线都是挂在Nginx下的, 这里这个就是为了本地启动一个服务
代码语言:javascript复制E:jsreact_redux>npm i serve -g
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.
added 89 packages, and audited 90 packages in 21s
23 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
E:jsreact_redux>
进入到项目文件夹执行 serve build(文件名) 就可以启动一个服务
这样就可以访问了
并且React的图标也变为线上模式了,而不是debug模式了