bunny笔记|React学习-Redux以及其相关插件的理解与
Redux适用于中大型项目开发进行统一的数据管理和维护。Redux独立与组件树,专门用于对数据的统一管理的集合。
Redux=Reducer + Flux
Flux是React原始用于数据管理的,由于存在多个store的问题,最终升级成Redux
Redux的工作流:
React Components -> Action Creators (转发action )-> Store (传入state和action)Action Creators ->(得到newState) Store ->(State变化重新渲染) ->React Components
redux.png
用Redux改造todoList案例
1)安装 yarn add redux (package.json中查看是否生成)
2)创建store index.js actionCreators.js actionType.js reducer.js
- index.js是reducer的入口(传入state和action)
import { createStore } from 'redux';
import reducer from './reducer';
//实例化store
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;
- reducer.js是store记录默认数据的仓库(得到newState)
//默认数据
const defaultState = {
todos: [
{ id: 1, title: '学习React课程', finished: false },
{ id: 2, title: '刷半小时电视剧', finished: false },
{ id: 3, title: '学习Java的课程', finished: false },
{ id: 4, title: '学习Python的课程', finished: false },
],
finishedCount: 0
}
export default (state = defaultState, action) => {
console.log(state, action);
return state
}
- App.js部分重复的数据就可以不要了
- 再到Compnents/List下改造
List.jsx
import store from '../store'
export default class List extends Component {
constructor(props){
super(props);
//获取redux中的数据
this.state = store.getState()
}
//...Other Part
}
render() {
console.log(this.state);
const{todos}=this.state;
//...Other Part
}
删除redux里的数据实现删除功能
- 在actionCreators.js中实现删除数据封装处理方法
注意:getDelItemAction封装数据 固定写法:export const 变量名=()=>({})
- 在actionTypes.js中做常量声明处理(常量声明是为了防止方法报错,有利于代码的健壮)
actionTypes.js
export const DEL_TODO_ITEM ='del_todo_item';//删除一条记录
- 回到actionCreators.js中 导入常量声明type,完成删除数据方法的封装处理
actionCreators.js
import { DEL_TODO_ITEM } from "./actionTypes"
//1.删除一条记录
//getDelItemAction封装数据 固定写法:export const 变量名=()=>({})
export const getDelItemAction=(todoId)=>({
type:DEL_TODO_ITEM,//要做什么事情,要做删除处理
todoId //根据todoId做数据删除
})
- 通过actionCreators.js的todoId,到reducer.js的action拿到todoId,然后对state做一些修改,最终再return 返回一个新的state实现修改后的操作
- 点击删除会触发到ctionCreators.js然后到reducer.js里找todoId进行处理
reducer.js
import { DEL_TODO_ITEM } from "./actionTypes"
//默认数据
const defaultState = {
todos: [
{ id: 1, title: '学习React课程', finished: false },
{ id: 2, title: '刷半小时电视剧', finished: false },
{ id: 3, title: '学习Java的课程', finished: false },
{ id: 4, title: '学习Python的课程', finished: false },
],
finishedCount: 0
}
export default (state = defaultState, action) => {
console.log(state, action);
//1.删除一条todo
if (action.type === DEL_TODO_ITEM) {
//深拷贝(原数据)
const newState = JSON.parse(JSON.stringify(state));
// 1.1 遍历
let finishedCount = 0;
newState.todos.forEach((todo, index) => {
if (todo.id === action.todoId) {
newState.todos.splice(index, 1);
}
});
// 1.2 处理选中的
newState.todos.forEach((todo, index) => {
if (todo.finished) {
finishedCount += 1;
}
});
// 1.3 更新状态
newState.finishedCount = finishedCount;
return newState;
}
return state
}
- 在components/List.jsx下实时监听页面,将删除处理后的数据渲染出来
import React, {Component} from 'react';
import Item from './Item';
import store from './../store';
export default class List extends Component {
constructor(props){
super(props);
// 获取redux中的数据
this.state = store.getState();
// 订阅store的改变
this._handleStoreChange = this._handleStoreChange.bind(this);
store.subscribe(this._handleStoreChange);
}
render() {
const {todos} = this.state;
return (
<ul className="todo-main">
{
todos.map((todo, index) => (
<Item
key={index}
todo={todo}
/>
))
}
</ul>
)
}
//更新Store
_handleStoreChange(){
// 更新状态
this.setState(store.getState())
}
}
综上,删除一条记录的方法已经redux的处理完成了。
store目录下即redux的文件
1)index.js 入口
2)actionCreators.js 定义行为和方法,把需要的数据传到reducer.js
3)reducer.js的action接收数据后对数据处理,实现逻辑功能
4)actionCreators.js 则是做为常量类型声明的管理集合
-
再回到componnets/Item.jsx实现页面中调用该方法实现整个流程的运转
-
点击
删除的触发方法在components/Item.jsx下render()里的button标签。要重新写处理删除一条记录的方法,绑定到oclick触发的方法。可以在onClick()构造一个方法用于处理删除一条数据的方法 -
要实现删除要把actionCreators.js下的action拿到 用Store传递一个action
onClick={()=>this._dealRemove(todo.id)}
并将该方法的实现在render()方法后面添加处理
Item.jsx
import store from '../store';
import {getDelItemAction} from '../store/actionCreators'
//删除一条记录
_dealRemove(todoId){
//拿到action
const action =getDelItemAction(todoId);
//固定写法。把action分发(传)下去
store.dispatch(action);
}
- 在components/List.jsx处理页面渲染实时监听
export default class List extends Component {
constructor(props){
super(props);
//获取redux中的数据
this.state = store.getState();
this._handeleStoreChange=this._handeleStoreChange.bind(this)
//订阅store的改变
store.subscribe(this._handeleStoreChange)
}
//...Other Part
//在render()方法的下面实现该监听处理的方法
_handeleStoreChange(){
//更新状态,进行页面的重新绘制
this.setState(store.getState())
}
}
总结:
- 第一步 把数据都抽到store。
store/index.js是统一对外的,所以这里要输出一个store
调用createStore()的方法把reducer关联起来
- 第二步 所有操作数据和状态的方法(操作action的方法)都在actionCreators.js统一封装处理
将actionCreators.js与actionTypes.js做关联(常量声明处理)
- 第三步 界面调用。怎么调用,主要是Component下的jsx文件
这里是Item.jsx和List.jsx
能结合redux第三方工具 更好理解一些
redux02.jpg
redux (2).png
选中redux里的数据条实现选中功能
第一步 先进入store/index.js,但是这里不需要做其它改变了
界面是触发一个action,然后通过Store用diaspatch()派发action给到reducer。然后reducer来改变最终的数据返回给我们的index.js,然后index.js再返回出来store。(终结者是store)
第二步 修改actionCreators.js和actionTypes.js
第三步 修改List.jsx和Item.jsx
其它处理可参考如上所述的处理流程。
以下是redux处理方法部分的源码(即store目录下的源码,全部源码可以到git下clone获取):
index.js
import { createStore } from 'redux';
import reducer from './reducer';
//实例化store
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export default store;
actionTypes.js
//常量声明是为了防止方法报错,有利于代码的健壮
export const DEL_TODO_ITEM ='del_todo_item';//删除一条记录
export const CHANGE_TODO_ITEM = 'change_todo_item'; // 修改一条记录的转态
export const ADD_TODO_ITEM = 'add_todo_item'; // 添加一条记录
export const REMOVE_FINISHED_TODO_ITEM = 'remove_finished_todo_item'; // 删除已经完成的所有任务
export const IS_CHECKED_ALL_TODO_ITEM = 'is_checked_all_todo_item'; // 全选和非全选
export const GET_ALL_ITEM = 'get_all_item'; // 获取所有记录
actionCreators.js
import {
DEL_TODO_ITEM,
CHANGE_TODO_ITEM,
ADD_TODO_ITEM,
REMOVE_FINISHED_TODO_ITEM,
IS_CHECKED_ALL_TODO_ITEM,
GET_ALL_ITEM
} from "./actionTypes"
//1.删除一条记录
//getDelItemAction封装数据 固定写法:export const 变量名=()=>({})
export const getDelItemAction = (todoId) => ({
type: DEL_TODO_ITEM,
todoId
})
// 2. 修改一条记录的状态
export const getChangeItemFinishedAction = (todoId, isFinished) => ({
type: CHANGE_TODO_ITEM,
todoId,
isFinished
});
// 3. 添加一条记录
export const getAddItemAction = (todo) => ({
type: ADD_TODO_ITEM,
todo
});
// 4. 删除已经完成的所有任务
export const getRemoveFinishedItemAction = () => ({
type: REMOVE_FINISHED_TODO_ITEM
});
// 5. 全选和非全选
export const getIsCheckedAll = (flag) => ({
type: IS_CHECKED_ALL_TODO_ITEM,
flag
});
reducer.js
import {
DEL_TODO_ITEM,
CHANGE_TODO_ITEM,
ADD_TODO_ITEM,
REMOVE_FINISHED_TODO_ITEM,
IS_CHECKED_ALL_TODO_ITEM
} from './actionTypes'
//默认数据
const defaultState = {
todos: [
{ id: 1, title: '学习React课程', finished: false },
{ id: 2, title: '刷半小时电视剧', finished: false },
{ id: 3, title: '学习Java的课程', finished: false },
{ id: 4, title: '学习Python的课程', finished: false },
],
finishedCount: 0
}
export default (state = defaultState, action) => {
console.log(state, action);
//1.删除一条todo
if (action.type === DEL_TODO_ITEM) {
//深拷贝(原数据)
const newState = JSON.parse(JSON.stringify(state));
// 1.1 遍历
let finishedCount = 0;
newState.todos.forEach((todo, index) => {
if (todo.id === action.todoId) {
newState.todos.splice(index, 1);
}
});
// 1.2 处理选中的
newState.todos.forEach((todo, index) => {
if (todo.finished) {
finishedCount += 1;
}
});
// 1.3 更新状态
newState.finishedCount = finishedCount;
return newState;
}
// 2. 修改一条记录的状态
if(action.type === CHANGE_TODO_ITEM){
// 1.1 遍历
const newState = JSON.parse(JSON.stringify(state));
let finishedCount = 0;
newState.todos.forEach((todo, index) => {
if (todo.id === action.todoId) {
todo.finished = action.isFinished;
}
if (todo.finished) {
finishedCount += 1;
}
});
// 2.3 返回新的数据状态
newState.finishedCount = finishedCount;
return newState;
}
// 3. 添加一条记录
if(action.type === ADD_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.todos.push(action.todo);
return newState;
}
// 4. 删除已经完成的所有任务
if(action.type === REMOVE_FINISHED_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state));
// 4.1 取出对象
let tempArr = [];
newState.todos.forEach((todo, index) => {
if (!todo.finished) {
tempArr.push(todo);
}
});
// 4.2 返回最新的状态
newState.todos = tempArr;
newState.finishedCount = 0;
return newState;
}
// 5. 全选和非全选
if(action.type === IS_CHECKED_ALL_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state));
// 5.1 遍历
let finishedCount = 0;
newState.todos.forEach((todo, index) => {
todo.finished = action.flag;
});
// 5.2 处理选中的
newState.todos.forEach((todo, index) => {
if (todo.finished) {
finishedCount += 1;
}
});
// 5.3 更新状态
newState.finishedCount = finishedCount;
return newState;
}
return state
}
网络异步
- 用node搭建服务器
- 前端获取接口即可
1)服务器端
1.搭建node的express服务器
下载node
全局安装expressnpm install express -g
创建服务器
express --view=ejs react-api
cd react-api
npm install
运行服务器npm run
此时共有两个文件目录:
Promise(用于前端请求接口)
react-api(基于node的后端服务器,提供接口和方法) 可进入bin目录下的www,将端口号修改为'5207',以防与3000冲突。
var port = normalizePort(process.env.PORT || '5207');
app.set('port', port);
这里的意思是要从环境中获取到端口,并存储于Express中以供使用。
- react-api保持打开运行的状态,再新建终端进入Promise目录下
- 在Promise目录下的src下新建一个用于存放api的文件夹,创建index.js和ajax.js两个文件。
index.js 入口文件
ajax.js 请求接口的文件
npm install axios或者yarn add axios安装axios
api/ajax.js
import axios from 'axios'
export default function ajax(url = '', params = {}, type= 'GET') {
let promise;
// 1. 返回promise对象
return new Promise((resolve, reject)=>{
// 1.1 判断请求的方式
if('GET' === type.toUpperCase()){
// 1.2 拼接字符串
let paramsStr = '';
Object.keys(params).forEach(key =>{
paramsStr += key + '=' + params[key] + '&'
});
// 1.3 过滤最后的&
if(paramsStr !== ''){
paramsStr = paramsStr.substr(0, paramsStr.lastIndexOf('&'));
}
// 1.4 拼接完整的路径
url += '?' + paramsStr;
// 1.5 发起get请求
promise = axios.get(url);
}else if('POST' === type.toUpperCase()){
promise = axios.post(url, params);
}
// 1.6 返回结果
promise.then((response)=>{
resolve(response.data);
}).catch(error=>{
reject(error);
})
})
}
api/index.js
import ajax from './ajax'
// 0. 定义基础路径
const BASE_URL = '/api';
// 1. 请求todo列表
export const getTodoList = () => ajax(BASE_URL + '/todos');
2)客户端
1.基于axios封装get/post
2)客户端 2.通过async和await接收promise
3.界面交互,最终把数据存入store中
redux-saga 和 redux-thunk
1)redux-thunk
概念:redux-thunk是一个redux的中间件,用来处理redux中的复杂逻辑,比如异步请求;
redux-thunk中间件可以让action创建函数不仅仅返回一个action对象,也可以是返回一个函数:
(说明:redux-dev-tools和redux-thunk兼容)
2)redux-saga
1.概念:
redux-saga是一个用于管理redux应用异步操作的中间件,redux saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。
原理:
- sagas是通过generator函数来创建的
Redux中间件 - sagas监听发起的action,然后决定基于这个action来做什么
3)在redux-saga中,所有的任务都通过用yield Effects来完成
4)redux-saga为各项任务提供了各种Effects创建器,让我们可以用同步的方式来写异步代码
安装:npm install redux-saga或yarn add redux-saga
react-redux
- Provider方法
Why? 避免Redux中store全局化,把store直接集成到React应用的顶层 props里面,方便各个子组件能访问到顶层props。解决手动监听state中数据改变store.subscribe(render)
核心 Provider和connect方法
Provider 将顶层组件包裹在Provider组件之中,所有组件就处于react-redux的控制之下了,store作为参数放到Provider组件中,方便其中所有子组件调用。
结构 <Provider>
React-Redux 1)Provider <Provider store = {store)>
<组件 />
目的 使得所有组件都能够访问到Redux中的数据
- connect方法
语法:
connect(mapStateToProps,mapDispatchToProps)(MyComponent)
mapStateToProps 把state映射到props中去,意思就是把Redux中的数据映射到React中的props中去。
映射:
const mapStateToProps=(state)=>{ return{
todos: state.todos
connect方法 mapStateToProps
}
}
渲染:
this.props.todos
目的:把Redux中的state变成React中的props
mapDispatchToProps
把各种dispatch也变成了props,在组件中可以直接使用。
mapDispatchToProps。避免了手动去用storesubscribe订阅render函数以达到更新页面的目的