Redux学习笔记
文章目录
1. redux理解
1.1 学习文档
-
英文文档: https://redux.js.org/
-
中文文档: http://www.redux.org.cn/
Github: https://github.com/reactjs/redux
1.2 redux是什么
-
redux是一个独立专门用于做状态管理的JS库(不是react插件库)
-
它可以用在react, angular, vue等项目中, 但基本与react配合使用
-
作用: 集中式管理react应用中多个组件共享的状态
1.3 redux工作流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3jPWx1oO-1588482452702)(redux工作流程.png)]
1.4 什么情况下需要使用redux
-
总体原则: 能不用就不用, 如果不用比较吃力才考虑使用 一般都要用
-
某个组件的状态,需要共享
-
某个状态需要在任何地方都可以拿到
-
一个组件需要改变全局状态
-
一个组件需要改变另一个组件的状态
2. redux的核心API
2.1. createStore()
- 作用:
创建包含指定reducer的store对象
- 编码:
import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)
2.2 store对象
-
作用: redux库最核心的管理对象
-
它内部维护着:
state
reducer
- 核心方法:
getState()
dispatch(action)
subscribe(listener)
- 编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)
2.3 applyMiddleware()
-
作用: 应用上基于redux的中间件(插件库)
-
编码:
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' // redux异步中间件
const store = createStore(
counter,
applyMiddleware(thunk) // 应用上异步中间件
)
2.4. combineReducers()
-
作用: 合并多个reducer函数
-
编码:
export default combineReducers({
user,
chatUser,
chat
})
3. redux的三个核心概念
3.1 action
-
标识要执行行为的对象
-
包含2个方面的属性
a. type: 标识属性, 值为字符串, 唯一, 必要属性
b. xxx: 数据属性, 值类型任意, 可选属性
- 例子:
const action = {
type: 'INCREMENT',
data: 2
}
- Action Creator(创建Action的工厂函数)
const increment = (number) => ({type: ‘INCREMENT’, data: number})
3.2 reducer
-
根据老的state和action, 产生新的state的纯函数
-
样例
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
}
}
- 注意
a. 返回一个新的状态
b. 不要修改原来的状态
3.3. store
-
将state,action与reducer联系在一起的对象
-
如何得到此对象?
import {createStore} from ‘redux’
import reducer from ‘./reducers’
const store = createStore(reducer)
- 此对象的功能?
getState(): 得到state
dispatch(action): 分发action, 触发reducer调用, 产生新的state
ubscribe(listener): 注册监听, 当产生了新的state时, 自动调用
4. 使用redux编写应用
4.1 下载依赖包
npm install --save redux
4.2 redux/action-types.js
/*
* 包含所有action.type的常量字符串
* */
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
4.3 redux/actions.js
/*
* 包含所有 action creater*/
import {INCREMENT,DECREMENT} from './action-types'
//增加
export const increment = (number) => ({
type:INCREMENT,data:number
})
//减少
export const decrement = (number) => ({
type:DECREMENT,data:number
})
4.5. redux/reducers.js
/*
* 包含n个reducer的模块
* */
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.data
case DECREMENT:
return state - action.data
default:
return state
}
}
4.6 components/app.jsx
import React, {Component} from "react";
import * as actions from '../redux/actions'
//或者 import {increment,decrement} from '../redux/actions'
export default class App extends Component { //默认暴露,
incremnet = () => {
// 1.得到选择增加数量
const number = this.choose.value * 1
// 2.调用store的方法更新状态
this.props.store.dispatch(actions.increment(number))
}
decrement = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 2.调用store的方法更新状态
this.props.store.dispatch(actions.decrement(number))
}
incrementIfOdd = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 2.得到原本的count状态
const count = this.props.store.getState()
// 3.判断,满足条件才更新
if (count % 2 === 1) {
// 4.更新state
this.props.store.dispatch(actions.increment(number))
}
}
incrementAsync = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 启动延时定时器
setTimeout(()=>{
// 2.更新state
this.props.store.dispatch(actions.increment(number))
},1000)
}
render() {
const count = this.props.store.getState()
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.choose = select}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.incremnet}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>increment if odd</button>
<button onClick={this.incrementAsync}>increment async</button>
</div>
</div>
)
}
}
4.7 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/app';
import store from "./redux/store";
import './index.css';
function render() {
ReactDOM.render(<App store={store}/>, document.getElementById('root'));
}
//初始化渲染
render()
//订阅监听(store中状态变化了,就会自动调用进行重绘)
store.subscribe(render)
4.8 redux/store.js
import {createStore} from 'redux'
import {counter} from './redux/reducer'
//生成store对象
const store = createStore(counter)//内部会第一次调用reducer函数,得到初始值0
console.log(store)
//暴露出去
export default store
4.9. 问题
-
redux与react组件的代码耦合度太高
-
编码不够简洁
由此需要引入react-redux插件开启第5节
5. react-redux
5.1. 理解
-
一个react插件库
-
专门用来简化react应用中使用redux
5.2. React-Redux将所有组件分成两大类
5.2.1 UI组件
a. 只负责 UI 的呈现,不带有任何业务逻辑
b. 通过props接收数据(一般数据和函数)
c. 不使用任何 Redux 的 API
d. 一般保存在components文件夹下
5.2.2 容器组件
a. 负责管理数据和业务逻辑,不负责UI的呈现
b. 使用 Redux 的 API
c. 一般保存在containers文件夹下
5.3. 相关API
5.3.1 Provider标签
让所有组件都可以得到state数据
<Provider store={store}>
<App />
</Provider>
5.3.2 connect()
用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
connect(
mapStateToprops,
mapDispatchToProps
)(Counter)
5.3.3 mapStateToprops()
将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
return {
value: state
}
}
5.3.4 mapDispatchToProps()
将分发action的函数转换为UI组件的标签属性
简洁语法可以直接指定为actions对象或包含多个action方法的对象
5.4. 使用react-redux
5.4.1 下载依赖包
npm install --save react-redux
5.4.2 redux/action-types.js 不变
5.4.3 redux/actions.js 不变
5.4.4 redux/reducers.js 不变
5.4.5 components/counter.jsx
import React, {Component} from "react";
import PropTypes from 'prop-types'
export default class Counter extends Component { //默认暴露,
static propTypes={
count:PropTypes.number.isRequired,
increment:PropTypes.func.isRequired,
decrement:PropTypes.func.isRequired
}
incremnet = () => {
// 1.得到选择增加数量
const number = this.choose.value * 1
// 2.调用store的方法更新状态
this.props.increment(number)
}
decrement = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 2.调用store的方法更新状态
this.props.decrement(number)
}
incrementIfOdd = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 2.得到原本的count状态
const count = this.props.count
// 3.判断,满足条件才更新
if (count % 2 === 1) {
// 4.更新state
this.props.increment(number)
}
}
incrementAsync = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
// 启动延时定时器
setTimeout(()=>{
// 2.更新state
this.props.increment(number)
},1000)
}
render() {
const {count} = this.props
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.choose = select}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.incremnet}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>increment if odd</button>
<button onClick={this.incrementAsync}>increment async</button>
</div>
</div>
)
}
}
5.4.6 containters/app.jsx
import React, {Component} from "react";
import {connect} from 'react-redux'
//import * as actions from '../redux/actions'
import {increment,decrement} from '../redux/actions'
import Counter from '../components/counter'
export default connect(
state =>({count:state}),
{increment,decrement}
)(Counter)
5.4.7 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'
import App from './containers/app';
import store from "./redux/store";
import './index.css';
ReactDOM.render((
<Provider store={store}>
<App />
</Provider>
), document.getElementById('root'));
5.5. 问题
-
redux默认是不能进行异步处理的,
-
应用中又需要在redux中执行异步任务(如ajax, 定时器)
由此需要引入异步中间插件开启第6节
6. redux异步编程
6.1 下载redux插件(异步中间件)
npm install --save redux-thunk
6.2 redux/actions.js
/*
* 包含所有 action creater
* 同步的action返回的都是一个对象
* 异步的action返回的是一个函数
* */
import {INCREMENT, DECREMENT} from './action-types'
//增加
export const increment = (number) => ({
type: INCREMENT, data: number
})
//减少
export const decrement = (number) => ({
type: DECREMENT, data: number
})
//异步action 只有用了中间件才能返回函数,否则只能默认返回对象
export const incrementAsync = (number) => {
return dispatch => {
//异步的代码
setTimeout(() => {
//1s后分发一个增加的action
dispatch(increment(number))
}, 1000)
}
}
6.3 components/counter.jsx
static propTypes={
count:PropTypes.number.isRequired,
increment:PropTypes.func.isRequired,
decrement:PropTypes.func.isRequired,
incrementAsync:PropTypes.func.isRequired
}
incrementAsync = () => {
// 1.得到选择减少数量
const number = this.choose.value * 1
this.props.incrementAsync(number)
}
6.4 containers/app.jsx
import {increment,decrement,incrementAsync} from '../redux/actions'
export default connect(
state =>({count:state}),
//(actions.increment, actions.decrement)
{increment,decrement,incrementAsync}
)(Counter)
6.5 redux/store.js
mport {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {counter} from './reducer'
//生成store对象,
const store = createStore(
counter,
applyMiddleware(thunk) // 应用上异步中间件
)//内部会第一次调用reducer函数,得到初始值0
//console.log(store)
//暴露出去
export default store
7. 使用redux调试工具
7.1. 安装chrome浏览器插件
7.2. 下载工具依赖包
npm install --save-dev redux-devtools-extension
7.3. 编码
import {createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import {counter} from './reducer'
//生成store对象,
const store = createStore(
counter,
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)//内部会第一次调用reducer函数,得到初始值0
//console.log(store)
//暴露出去
export default store
8. 相关重要知识: 纯函数和高阶函数
8.1 纯函数
-
一类特别的函数: 只要是同样的输入,必定得到同样的输出
-
必须遵守以下一些约束
a. 不得改写参数
b. 不能调用系统 I/O 的API
c. 能调用Date.now()或者Math.random()等不纯的方法
- reducer函数必须是一个纯函数
8.2 高阶函数
- 理解: 一类特别的函数
a. 情况1: 参数是函数
b. 情况2: 返回是函数
-
常见的高阶函数:
a. 定时器设置函数
b. 数组的map()/filter()/reduce()/find()/bind()
c. react-redux中的connect函数
-
作用:
a. 能实现更加动态, 更加可扩展的功能
9.combineReducers 管理多个reducer函数
reduer.js
import {combineReducers} from 'redux'
import {
ADD_COMMENT,
DELETE_COMMENT,
RECEIVE_COMMENTS
} from './action-types'
const initComments = []
//函数1
function comments(state = initComments, action) {
switch (action.type) {
case ADD_COMMENT:
return [...state, action.data]
case DELETE_COMMENT:
return state.filter((c, index) => index !== action.data)
case RECEIVE_COMMENTS:
return action.data
default:
return state
}
}
//函数2
function counter(state = 0, action) {
console.log('counter()', state, action)
switch (action.type) {
case INCREMENT:
return state + action.data
case DECREMENT:
return state - action.data
default:
return state
}
}
export default combineReducers({
comments,couner
})
store.js
import reducers from './reducer'
export default createStore(
reducers, composeWithDevTools(applyMiddleware(thunk))
)
app.jsx
export default connect(
state=>({comments:state.comments}),//state就是一个comments数组,因为state已经有了两个状态了
{addComment,deleteComment,getComments}
)(App)
10. redux改编comments项目
10.1 components/comment-add.jsx
import React, {Component} from "react";
import PropTypes from 'prop-types'
export default class CommentAdd extends Component{ //默认暴露,
static propTypes = {
addComment:PropTypes.func.isRequired
}
state = {
username:'',
content:''
}
//一直bind过于麻烦,可以使用箭头函数,现在箭头函数没有自己的this,就看外围this正好是组件对象
handleSubmit = () =>{
//收集数据,并封装成comment对象
const comment = this.state
//更新状态
this.props.addComment(comment)
//清除输入数据
this.setState({
username:'',
content:''
}
)
}
handleNameChange = (event) =>{
const username = event.target.value
this.setState({username})
}
handleContentChange = (event) =>{
const content = event.target.value
this.setState({content})
}
render() {
const {username,content} = this.state
return (
<div className="col-md-4" >
<form className="form-horizontal">
<div className="form-group">
<label>用户名</label>
<input type="text" className="form-control" placeholder="用户名"
value={username} onChange={this.handleNameChange}/>
</div>
<div className="form-group">
<label>评论内容</label>
<textarea className="form-control" rows="6" placeholder="评论内容"
value={content} onChange={this.handleContentChange}></textarea>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="button" className="btn btn-default pull-right"
onClick={this.handleSubmit}>提交</button>
</div>
</div>
</form>
</div>
)
}
}
10.2 components/comment-item.jsx
import React, {Component} from "react";
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import './commentItem.css'
import {deleteComment} from '../../redux/actions'
class CommentItem extends Component { //默认暴露,
static propTypes = {
comment: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
deleteComment: PropTypes.func.isRequired
}
handleClick = () => {
const {comment, index, deleteComment} = this.props
//提示
if (window.confirm(`确定删除${comment.username}的评论吗?`)) {
deleteComment(index)
}
}
render() {
//let comment = this.props.comment
const {comment} = this.props
//alert({comment}.length)
//alert(Object.keys({comment}).length)
//console.log(Object.keys({comment}))
return (
<li className="list-group-item">
<div className="handle">
<a href="javascript:;" onClick={this.handleClick}>删除</a>
</div>
<p className="user"><span>{comment.username}</span><span>说:</span></p>
<p className="centence">{comment.content}</p>
</li>
)
}
}
//因为这里要用到删除方法,所以要用connect函数传入方法
export default connect(
null,
{deleteComment}
)(CommentItem)
10.3 components/comment-list.jsx
import React, {Component} from "react";
//这个包需要下载,命令 npm install --save prop-types
import PropTypes from 'prop-types'
import CommentItem from "../comment-item/comment-item";
import './commentList.css'
export default class CommentList extends Component{ //默认暴露,
//不加static是给组件对象添加,加上是给组件类CommentList指定属性
static propTypes = {
comments: PropTypes.array.isRequired,
//delete: PropTypes.func.isRequired
}
render() {
const {comments,deleteComment} = this.props
//let comments = this.props.comments
//计算出是否显示
const display = comments.length === 0 ? 'block' : 'none'
return (
<div className="col-md-8">
<h3 className="reply">评论回复:</h3>
<h2 style={{display}}>暂无评论,点击左侧添加评论!!!</h2>
<ul className="list-group">
{
comments.map((comment,index)=><CommentItem comment={comment} key={index}
index={index} />)
}
</ul>
</div>
)
}
}
10.4 container/app.jsx
import React, {Component} from "react";
import {PropTypes} from 'prop-types'
import {connect} from 'react-redux'
import CommentAdd from "../../components/comment-add/comment-add";
import CommentList from "../../components/comment-list/comment-list";
import {addComment,deleteComment,getComments} from "../../redux/actions";
class App extends Component { //默认暴露,
static propTypes = {
comments:PropTypes.array.isRequired,
addComment:PropTypes.func.isRequired,
deleteComment:PropTypes.func.isRequired,
getComments:PropTypes.func.isRequired
}
componentDidMount() {
this.props.getComments()
}
render() {
const {comments,addComment,deleteComment} = this.props
return (
<div id="app">
<div>
<header className="site-header jumbotron">
<div className="container">
<div className="row">
<div className="col-xs-12">
<h1>请发表对React的评论</h1>
</div>
</div>
</div>
</header>
<div className="container">
<CommentAdd addComment ={addComment}/>
<CommentList comments={comments} deleteComment={deleteComment}/>
</div>
</div>
</div>
)
}
}
export default connect(
state=>({comments:state.comments}),//state就是一个comments数组
{addComment,deleteComment,getComments}
)(App)
10.5 redux/action-types.js
/*
* actions的type常量
* */
export const ADD_COMMENT = 'add_comment'
export const DELETE_COMMENT = 'delete_comment' //大小写快捷键转换 ctrl+shift+x
export const RECEIVE_COMMENTS = 'receive_comments'
10.6 redux/actions.js
/*
* 包含所有工厂函数*/
import {ADD_COMMENT, DELETE_COMMENT, RECEIVE_COMMENTS} from './action-types'
export const addComment = (comment) => ({
type: ADD_COMMENT, data: comment
})
export const deleteComment = (index) => ({
type: DELETE_COMMENT, data: index
})
const receiveComments = (comments) => ({
type: RECEIVE_COMMENTS, data: comments
})
export const getComments = () => {
return dispatch => {
setTimeout(() => {
const comments = [
{username: 'Tom', content: 'React挺好的!'},
{username: 'Mary', content: 'React太难了!'}
]
dispatch(receiveComments((comments)))
},1000)
}
}
10.7 redux/reducer.js
/*
* 包含n个reduer函数,根据老的state和action返回新的state*/
import {combineReducers} from 'redux'
import {ADD_COMMENT, DELETE_COMMENT,RECEIVE_COMMENTS} from './action-types'
const initComments = [
/* {username: 'Tom', content: 'React挺好的!'},
{username: 'Mary', content: 'React太难了!'}*/
]
function comments(state = initComments, action) {
switch (action.type) {
case ADD_COMMENT:
return [action.data, ...state]
case DELETE_COMMENT:
/*
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
*/
return state.filter((c, index) => index !== action.data)
case RECEIVE_COMMENTS:
return action.data
default:
return state
}
}
//函数2
function counter(state = 0, action) {
console.log('counter()', state, action)
switch (action.type) {
case 1:
return state + action.data
case 2:
return state - action.data
default:
return state
}
}
export default combineReducers({
comments,counter
})
//redux向外暴露的是一个什么结构?
//是一个对象,有两个属性:counter:数值属性,comments:数组
10.8 redux/store.js
/*
* redux最核心的管理对象store*/
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducer'
export default createStore(
reducers, composeWithDevTools(applyMiddleware(thunk))
)
10.9 index.js
import React from "react";
import ReactDOM from "react-dom"
import {Provider} from 'react-redux'
import App from './container/app/app'
import store from './redux/store'
ReactDOM.render((
<Provider store={store}>
<App/>
</Provider>
), document.getElementById('root'))
eturn action.data
default:
return state
}
}
//函数2
function counter(state = 0, action) {
console.log('counter()', state, action)
switch (action.type) {
case 1:
return state + action.data
case 2:
return state - action.data
default:
return state
}
}
export default combineReducers({
comments,counter
})
//redux向外暴露的是一个什么结构?
//是一个对象,有两个属性:counter:数值属性,comments:数组
10.8 redux/store.js
/*
* redux最核心的管理对象store*/
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import reducers from './reducer'
export default createStore(
reducers, composeWithDevTools(applyMiddleware(thunk))
)
10.9 index.js
import React from "react";
import ReactDOM from "react-dom"
import {Provider} from 'react-redux'
import App from './container/app/app'
import store from './redux/store'
ReactDOM.render((
<Provider store={store}>
<App/>
</Provider>
), document.getElementById('root'))