redux
Redux基础
安装
-
npx create-react-app
:安装 react -
yarn add redux
:生产环境安装 - 部分使用:项目部分数据使用 redux 管理。全局使用:项目中所有数据全使用 redux 管理。项目一旦使用 redux 就说明项目数据量一定是大的,推荐全局使用
- 注意 React 层获取 state 不是 state.js 内,而是通过订阅-发布从 store 获取,而 store的 state 值是 reducer 提供的
手动撸一个输入添加到列表的东西
目录结构:
src
store
index.js 创建 store 对象
actionCreator.js 动作创建者
reducer.js 函数,通过动作设置 state
state.js state 设定初始值
type.js 这就是来搞笑的(高大上的)
page
todos
index.css 设置样式
index.js 对 todos 所有组件一起导出
TodosContent.js 组件
TodosInput.js 组件
1. store/index.js
:创建 store 实例
1.从redux模块结构出 createStore 用于创建 store 实例
import { createStore } from 'redux'
import reducer from './reducer'
2.这里接受一个函数做参数,需要打造 reducer 函数
const store = new createStore( reducer )
export default store
实例出来的store
对象有 dispatch 和 getState方法。区别 flux
dispatch: ƒ dispatch(action)
getState: ƒ getState()
replaceReducer: ƒ replaceReducer(nextReducer)
subscribe: ƒ subscribe(listener)
Symbol(observable): ƒ observable()
2. store/state.js
:设置 state 初始值
const state = {
todos:[
{id:1,text:'学习'},
{id:2,text:'LOL'}
]
}
export default state
3. store/reducer.js
:纯函数,根据动作改变 state
这里reducer是一个纯函数,接受两个参数。第一个参数是之前的状态,一个是aciton
reducer 函数的返回值是什么,store 的初始值就是什么
import state from './state'
import * as type from './type'
const reducer = (previousState = state, action) => { 这里给previousStare传默认值
var newState = {
...previousState 老师说这是深拷贝,避免直接对 state 操作
}
switch (action.type) { 通过对 action.type 判断,对 state 做不同处理
case type.INCREMENT:
newState.todos.push({
id: newState.todos.length + 1,
text: action.payload
});
break;
default: break;
}
return newState 返回给 state
}
export default reducer
4. store/actionCreator.js
:创造 aciton
对象,提交给 reducer
import * as type from './type'
import store from '.';
const actionCreator ={
addTodos(val) {
let action = {
type: type.INCREMENT,
payload: val
}
store.dispatch( action,val )
这里的 dispatch 由 store提供,区别 flux 手动打造
store.dispatch( action,val )等同于将 payload 写在 action 内
/*
let action ={
type:type.ADD_TODOS,
payload:val
}
*/
}
}
export default actionCreator
5. store/type.js
export const INCREMENT = 'INCREMENT'
1. page/todos/TodoInput.js
import React from 'react'
import actionCreator from '../../store/actionCreator'
class TodosInput extends React.Component {
add = e => { 添加onkeyup的事件处理程序
if (e.keyCode === 13) { 判断 keyCode 确定回车键
actionCreator.increment(this.refs.todos_input.value) 触发 actionCreator
this.refs.todos_input.value = '' 清空输入框
}
}
render() {
return (
<div className="TodosContent">
<input
type="text"
placeholder="请输入待办事项"
通过 ref 绑定 DOM,这里 ref 也可以接受一个回调函数
// ref={ el => this.todosinput = el }
ref='todos_input'
onKeyUp={this.add} 绑定事件
/>
</div>
)
}
}
export default TodosInput
2. page/todos/TodoInput.js
import React from 'react'
import store from '../../store'
function Item(props) { 创建 Item 无状态组件
return (
<li>{props.item.text}</li> 通过 props 设置 li 标签内容
)
}
class TodosContent extends React.Component {
constructor() {
super()
this.state = {
todos: store.getState().todos 通过 store.getState() 获取 state 值
}
}
renderItem = () => { 设置一个方法,返回一个结构
return this.state.todos.map(item => { 遍历状态 获取结构,
return (
<Item item={item} key={item.id} /> 使用无状态组件,分离结构和逻辑,这里直接写<li></li>也是可以
)
})
}
render() {
return (
<div className="TodosContent">
{this.renderItem()} 调用方法获取结构
</div>
)
}
componentWillMount() { 通过钩子订阅事件
store.subscribe(() => { 事件订阅,当 state 改变,需要用这个更新状态
状态不会自动更新,上边 constructor 内 this 是写死的。
需要重新对状态修改,状态的修改不能直接赋值,需要 this.setState() 方法,接受一个对象即新状态
this.setState({
todos: store.getState().todos
})
})
}
}
export default TodosContent
3. page/todos/index.js
import TodosContent from './TodosContent'
import TodosInput from './TodosInput'
import './index.css'
export { TodosContent, TodosInput }
Redux 进阶
1. combineReducers
数据模块划分
Redux
数据模块划分
在实际中,我们对数据分块。一个项目中存在很多数据,我们不能都放在 state 中,这样导致混乱。我们将整个数据划分,通过中间件我们可以统一管理不同的 reducer 。因为reducer 是真正管理数据的地方。
store
目录结构
store
home
// index.js 不需要 indexed.js 只需要一个总体的 store
type.js
state.js
reducer.js
acttionCreators.js
todos
type.js
state.js
reducer.js
acttionCreators.js
mine
...
index.js
//state.js 不需要总体的 state 分片自己的 state
reducer.js
import { combineReducers } from 'redux'
import home from './home/reducer'
const reducer = combineReducers({ home })
export default reducer
acttionCreators.js
2. react-redux
- 依赖:
yarn add redux
- 安装:
yarn add react-redux
- 在根组件最外层嵌套
Provider
组件,并将 store 当作属性传给 Provider - 利用 connect 方法使用 store 相关 api
- 作用:简化操作流程,相当于 vuex 的 map什么鬼,忘了
在使用 redux 中,我们发现有几个动作老师重复的,组件想使用 store 数据,必须在constructor 中利用 store.getState() 获取在挂载在自己状态上。我们要通过 actionCreator 中请求数据,然后发送给前台。
组件想要更新状态需要在 componentWillMount(){} 钩子中利用 store.subscribe() 方法注册一个监听函数,然后获取最新状态后再使用 this.setState() 方法更新自己的状态。如此繁琐的步骤我选择放弃~~
我们引入 react-redux 工具来处理这些问题
react-redux 只是一个辅助工具,将 react 和redux 更好的连接。如果一个组件想要使用store中的数据或者actionCreator的方法,我们都应该其变化为容器组件包裹UI组件的样子。其中,容器组件负责拦截 store,将状态、方法传递给 UI 组件,UI 组件从属性上获取通过 api 后直接使用。
核心API
Provider:传递数据
connect:生成容器组件
容器组件、UI 组件
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展示(骨架、样式) | 描述如何运行(数据获取,状态更新) |
直接使用Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props调用回调函数 | 向Redux 派发 action |
调用方式 | 手动 根据connet生成ui组件 | 由 React Redux 自动生成 |
1.代码重复
组件内状态获取
事件订阅
action发送
解决:使用
2.异步
如果异步,则上述解决办法失效,还用重新写
解决:使用中间件
使用
src/index.js
react-redux 帮我们 获取 store 并且帮我们订阅(响应式)
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}> 包裹根组件,传递 store
<App />
</Provider>
, document.getElementById('root'));
组件.js
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux';
import actionCreator from '../../store/actionCreator'
class Mine extends React.Component{
add=()=>{
actionCreator.add()
}
render(){
console.log(this.props.dispatch)
return (
<div>
<h3>Mine</h3>
<p> count:{this.props.count} </p>
<button onClick={this.add}>+</button>
<p> name:{this.props.name} </p>
</div>
)
}
}
监听 Redux store 的变化。只要 Redux store 发生改变,mapStateToProps 函数就会被调用。
该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
const mapStateToProps = (state)=>{
如果 state 只有一层,数据直接在 state 内
return state
如果数据有两次:state={ mine:{name:'zhangsan',count:1} }
return {
mine:{...state.mine}
}
}
将一些能用到 dispatch 的组件传递过去,并且发送动作由这里完成
const mapDispatchToProps = (dispatch)=>{
return bindActionCreators(actionCreator,dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(Mine)
actionCreator.js
const actionCreator ={
1.这么写
increment(){
let action = {
type:'INCREMENT'
}
return action
}
2.或者直接这么写也可以
increment(val){
return {
type:type.INCREMENT,
payload:val
}
}
}
export default actionCreator
3. redux-thunk
异步请求
- 安装
yarn add redux-thunk
- 创建
store
时使用中间件
action 只是一个对象,不被期望包含异步操作,存在难以维护,业务逻辑不清晰。
使用中间件后,可以解决上述问题,常见的 actionCreator
异步库:
Redux-thunk(主流)
Redux-saga
Redux-effects
Redux-loop
…
基于 promise 的异步库:
Redux-promise
Redux-promises
如果没有异步工具,我们异步获取数据会是下面这样的
组件.js
:
import React from 'react'
import actionCreator from '../../store/home/actionCreator'
import store from '../../store'
class Name extends React.Component{
constructor(){
super()
this.state={
name:''
}
}
1.这里是老师上课讲的关于异步获取步骤,我觉得不行
async componentDidMount(){
这里调用动作发送请求获取和修改数据,异步
await actionCreator.getName()
这里订阅获取数据
store.subscribe(()=>{
这里对自己的状态修改,同步。所以要等异步处理完成后在修改自身状态
this.setState({
name:store.getState().home.name
})
})
}
2.这是我自己测试的,测试成功。不需要 async 和await 本身事件订阅接受的就是个回调函数
在组件挂在前发送数据,然后订阅如果
componentDidMount(){
actionCreator.getName()
store.subscribe(()=>{
this.setState({
name:store.getState().home.name
})
})
}
render(){
return(
<div>
<h1> redux 异步请求 </h1>
<button>获取</button>
<p>Name:{this.state.name.name}</p>
</div>
)
}
}
export default Name
actionCreatore.js
import store from "../";
const actionCreator = {
getName() {
fetch('/data.json')
.then(res => res.json())
.then(data => {
let action = {
type: 'GETNAME',
payload: data
}
store.dispatch(action)
})
.catch(err => console.log(err))
}
}
export default actionCreator
使用 异步处理
improt {createState, applyMiddleware } from 'redux'
imorpt thunk from 'redux-thunk'
imorpt reducer from './reducer'
const store = new createStore( reducer, applyMiddleware(thunk) )
export default store
actionCreator.js