React-Redux 的使用
- redux是数据框架。
- React-Redux 是第三方模块,他可以让我们在react中更方便的使用redux。
安装React-Redux
首先我们需要将之前的项目删除到只剩下src文件夹下的index.html。
然后安装React-Redux :
(如果还未安装redux则需要先使用yarn add redux
安装redux)
新建组件TodoList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { Component } from 'react'
class TodoList extends Component{ render(){ return( <div> <div> <input /> <button>提交</button> </div> <ul> <li>Dell</li> </ul> </div> ) } }
export default TodoList ;
|
新建store(使用redux)
在react中,我们最好使用store来管理数据,那么我们就需要使用redux的createStore()创建一个store。
新建store文件夹并在其下新建index.js:
1 2 3 4 5
| import { createStore } from "redux";
const store = createStore();
export default store;
|
新建reducer.js
- store需要借助 reducer 来处理action以改变state。所以我们需要新建reducer.js并在index.js中引入reducer。
- reducer.js是纯函数,返回一个新的state给store,函数的参数1是store的state,参数2是action。 一般我们会定义一个state初始值。
新建reducer.js:
1 2 3 4 5 6 7 8
| const defaultState = { inputValue: "", list: [] }
export default (state = defaultState, action) => { return state; }
|
在index.js中引入reducer
createStore()创建一个store时我们就要引入reducer作为参数。
index.js:
1 2 3 4 5 6
| import { createStore } from "redux"; import reducer from "./reducer";
const store = createStore(reducer);
export default store;
|
在组件中使用store的数据
原本的数据使用方法
- 在组件中引入store并创建constructor构造函数,在构造函数内使用store的getState()获取到store的state并赋值给组件的state。
- 在需要使用数据的元素上通过调用组件的state来使用store的state中的值。
- (在reducer中给store的state一个inputValue的初始值为“你好呀”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { Component } from 'react'; import store from "./store";
class TodoList extends Component { constructor(props) { super(props); this.state = store.getState(); }
render() { return ( <div> <div> <input value={this.state.inputValue} /> <button>提交</button> </div> <ul> <li>Dell</li> </ul> </div> ) } }
export default TodoList;
|
使用react-redux之后的数据使用方法
- 在src的index.js中引入react-redux的 Provider组件和store,通过Provider组件使所有被其包裹的组件有能力与store连接。
- 在组件中引入react-redux的connect方法,在组件输出时通过connect方法连接store。
- connect()的参数1(mapStateToProps())定义store中的数据state与组件的props之间的映射关系。
- 在组件中通过
this.props.数据名
引用store中的数据。
- connect()的参数2(回调函数)接收dispatch作为参数,在参数2(mapDispatchToProps())内定义action并将其传给store。
总结:
- Provider组件让TodoList有能力连接上store
- connect()让TodoList连接store
- 如何连接看 mapStateToProps(),修改state看 mapDispatchToProps()
Provider组件
react-redux提供一个 Provider组件 ,该组件可以连接store,并且使得被包含在该组件下的所有组件都有能力连接store:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React from 'react'; import ReactDOM from 'react-dom'; import TodoList from './TodoList'; import { Provider } from "react-redux"; import store from "./store";
const App = ( <Provider store={store}> <TodoList /> </Provider> )
ReactDOM.render(App, document.getElementById('root'));
|
原本我们在src目录下的index.html中只将TodoList组件渲染在了页面上。
现在我们可以引入react-redux的 Provider组件,将其放在App组件(JSX语法)中,将我们所有需要用到store数据的组件都放在Provider组件中。
最后页面只渲染App组件即可.【**注意:这里渲染用的是App
,不是<App />
**】
connect方法
- Provider组件 只是使得被包含的组件有能力连接store,但怎么连接就要依靠connect方法
- connect方法让被 Provider组件 包裹的组件连接上store,从而可以直接使用store中的数据。
- connect方法的参数1
mapStateToProps
(回调函数):规定组件的props和store之间怎么做连接的规则。
- 注意:使用了connect方法后,使用
this.props.xxx
调用store中的数据。
- connect方法的参数2
mapDispatchToProps
(回调函数):将store的dispatch()传到组件的props中,使props中的函数可以调用dispatch(),所以可以在该函数中调用dispatch将action传给store。然后在reducer中修改state的值。【修改action的操作放在参数2中】
- 想让xx组件连接上store,则原本导出的是xx组件,现在我们**导出的是 connect()生成的组件
connect(mapStateToProps,mapDispatchToProps)(xx)
**。
总结
- 调用
connect(mapStateToProps,mapDispatchToProps)()
返回值是Connect组件(请注意大小写的区别)。
connect(mapStateToProps,mapDispatchToProps)(需要连接store的组件名)
返回的是组件。
- 通俗点理解,使用connect可以把state和dispatch绑定到react组件,使得组件可以访问到redux的数据。
- connect让TodoList连接store
- 如何连接看mapStateToProps(),修改state看mapDispatchToProps()
使输入框内实时显示输入的内容
TodoList.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import React, { Component } from 'react'; import { connect } from "react-redux";
class TodoList extends Component { render() { return ( <div> <div> <input value={this.props.inputValue} onChange={this.props.changeInputValue} /> <button>提交</button> </div> <ul> <li>Dell</li> </ul> </div> ) } }
// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中 const mapStateToProps = (state) => { return { // state中的inputValue映射到props的inputValue中 inputValue: state.inputValue } }
// 将store的dispatch()传到组件的props中 const mapDispatchToProps = (dispatch) => { return { changeInputValue(e){ const action={ type:"change_input_value", value:e.target.value } dispatch(action); } } }
// connect让TodoList连接store // 如何连接看mapStateToProps(),修改state看mapDispatchToProps() export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
|
reducer.js:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const defaultState = { inputValue: "你好呀", list: [] }
export default (state = defaultState, action) => { if (action.type === "change_input_value") { const newState = JSON.parse(JSON.stringify(state)); newState.inputValue = action.value; return newState; } return state; }
|
使用React-redux完成TodoList功能
好处:
使用connect()连接组件和store后,每次store更新就会带动组件的state更新,页面渲染不需要通过store.subscribe()订阅store了。
点击“确认”增加list中item
TodoList.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import React, { Component } from 'react'; import { connect } from "react-redux";
class TodoList extends Component { render() { return ( <div> <div> <input value={this.props.inputValue} onChange={this.props.changeInputValue} /> <button onClick={this.props.handleClick}>提交</button> </div> <ul> { this.props.list.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> ) } }
// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中 const mapStateToProps = (state) => { return { // state中的inputValue映射到props的inputValue中 inputValue: state.inputValue, list: state.list } }
// 将store的dispatch()传到组件的props中 const mapDispatchToProps = (dispatch) => { return { changeInputValue(e) { const action = { type: "change_input_value", value: e.target.value } dispatch(action); }, handleClick() { const action = { type: "add_item" } dispatch(action); } } }
// connect让TodoList连接store,如何连接看mapStateToProps(),修改state看mapDispatchToProps() export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
|
reducer.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const defaultState = { inputValue: "", list: [] }
export default (state = defaultState, action) => { if (action.type === "change_input_value") { const newState = JSON.parse(JSON.stringify(state)); newState.inputValue = action.value; return newState; } if (action.type === "add_item") { const newState = JSON.parse(JSON.stringify(state)); newState.list.push(newState.inputValue); newState.inputValue=""; return newState; } return state; }
|
点击“提交”后页面list中item增加:
优化代码
- TodoList是一个UI组件,所以我们可以将它变成一个无状态组件,依次提高性能。
- 在TodoList.js中我们输出的是经过connect()合成以后的 容器组件(加上参数1、2后 他是有逻辑的)。
- 所以使用了react-redux以后,我们将逻辑和页面写在了一个TodoList.js中,不像之前那样分为TodoList.js和TodoListUI.js。
优化步骤:
- TodoList 没有逻辑,他是一个UI组件,所以我们可以将TodoList组件优化为无状态组件。
- 无状态组件:使用常量const定义一个箭头函数,该函数接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容
- 使用了connect()的参数1、2后,store中state的数据都映射在props上,涉及dispatch传递action的函数也都在props上。所以我们可以使用ES6的解构赋值将所有
this.props.xxx
形式的变量/函数都简化为const {xxx,xxx} = props
,接着调用时直接使用 变量/函数名 即可。
- 在解构赋值中,由于无状态组件接收一个参数props,所以此时**
this.props
变成props
**。
TodoList.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import React from 'react'; import { connect } from "react-redux";
const TodoList = (props) => { const { inputValue, list, changeInputValue, handleClick } = props; return ( <div> <div> <input value={inputValue} onChange={changeInputValue} /> <button onClick={handleClick}>提交</button> </div> <ul> { list.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> ) }
// state指store中的数据,通过mapStateToProps()将state中的数据映射到props中 const mapStateToProps = (state) => { return { // state中的inputValue映射到props的inputValue中 inputValue: state.inputValue, list: state.list } }
// 将store的dispatch()传到组件的props中 const mapDispatchToProps = (dispatch) => { return { changeInputValue(e) { const action = { type: "change_input_value", value: e.target.value } dispatch(action); }, handleClick() { const action = { type: "add_item" } dispatch(action); } } }
// connect让TodoList连接store,如何连接看mapStateToProps(),修改state看mapDispatchToProps() export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
|
connect()与componentDidMount()
原本,connect 将store的state和dispatch包装在无状态组件TodoList上,使其成为聪明的容器组件。
那么我们想要使用componentDidMount()时:
- 先将 无状态组件TodoListUI 包装在 容器组件TodoList 中
- 在该 容器组件 中使用componentDidMount()
- 最后使用connect包装该 容器组件,形成新的容器组件
注意无状态组件调用store相关的变量/方法
- 我们原本使用connect直接包裹 无状态组件 ,无状态组件中可以通过props拿到store中的变量/方法。
- 现在使用 容器组件TodoList 包裹 无状态组件TodoListUI ,那么无状态组件就没办法自己拿到store中的变量/方法。需要容器组件(父组件)在调用子组件(无状态组件TodoListUI)时通过属性的方式将容器组件拿到的store中的变量/方法 传给子组件(无状态组件TodoListUI),子组件才能调用store中的变量/方法。
点击删除功能和mock.js模拟数据
接下来补全了actionTypes.js、actionCreator.js、mock-TodoListMock.js,并使用了redux-thunk中间件。
store文件夹-index.js 加入redux-thunk中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createStore,applyMiddleware,compose } from "redux"; import reducer from "./reducer"; import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers( applyMiddleware(thunk), );
const store = createStore(reducer,enhancer);
export default store;
|
store文件夹-actionTypes.js 规定action的type值为常量,防止书写错误不报错:
1 2 3 4
| export const CHANGE_INPUT_VALUE = "change_input_value" export const ADD_ITEM = "add_item" export const DELETE_ITEM = "delete_item" export const INIT_LIST = "init_list"
|
store文件夹-actionCreator.js 统一创建action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM,INIT_LIST } from "./actionTypes" import axios from "axios"; import "../mock/TodoListMock";
export const getChangeInputValue = (e) => { return ( { type: CHANGE_INPUT_VALUE, value: e.target.value } ) }
export const getAddItem = () => { return ( { type: ADD_ITEM, } ) }
export const getDeleteItem = (index) => { return ( { type: DELETE_ITEM, index } ) }
export const initListAction = (data) => { return ( { type: INIT_LIST, data } ) }
export const getTodoList = ()=>{ return(dispatch)=>{ // 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据 axios.get("/list.json").then((res) => { const data = res.data.list; // 通过initListAction()将data转为action对象 const action = initListAction(data); // 将action对象传给store dispatch(action); }).catch(() => { alert("失败"); }) } }
|
store文件夹-reducer.js 处理action返回newState:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, INIT_LIST } from "./actionTypes"
const defaultState = { inputValue: "", list: [] }
export default (state = defaultState, action) => { if (action.type === CHANGE_INPUT_VALUE) { const newState = JSON.parse(JSON.stringify(state)); newState.inputValue = action.value; return newState; } if (action.type === ADD_ITEM) { const newState = JSON.parse(JSON.stringify(state)); newState.list.push(newState.inputValue); newState.inputValue = ""; return newState; } if (action.type === DELETE_ITEM) { const newState = JSON.parse(JSON.stringify(state)); newState.list.splice(action.index, 1); return newState; } if (action.type === INIT_LIST) { const newState = JSON.parse(JSON.stringify(state)); newState.list = action.data; return newState; } return state; }
|
mock文件夹-TodoListMock.js 创建随机模拟数据:
1 2 3 4 5 6
| import Mock from "mockjs"
export default Mock.mock("/list.json",{ "list|3":["@cword(3,10)"] })
|
TodoLisy.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| import React, { Component } from 'react'; import { connect } from "react-redux"; import { getChangeInputValue, getAddItem, getDeleteItem, getTodoList } from "./store/actionCreator";
const TodoListUI = (props) => { const { inputValue, list, changeInputValue, handleClick, handleClickDelete } = props; return ( <div> <div> <input value={inputValue} onChange={changeInputValue} /> <button onClick={handleClick}>提交</button> </div> <ul> { list.map((item, index) => { return <li key={index} onClick={() => (handleClickDelete(index))}>{item}</li> }) } </ul> </div> ) }
class TodoList extends Component { render() { const { inputValue, list, changeInputValue, handleClick, handleClickDelete } = this.props; return ( <TodoListUI inputValue={inputValue} list={list} changeInputValue={changeInputValue} handleClick={handleClick} handleClickDelete={handleClickDelete} /> ) } componentDidMount() { this.props.initList(); } }
const mapStateToProps = (state) => { return { inputValue: state.inputValue, list: state.list } }
const mapDispatchToProps = (dispatch) => { return { changeInputValue(e) { const action = getChangeInputValue(e); dispatch(action); }, handleClick() { const action = getAddItem(); dispatch(action); }, handleClickDelete(index) { const action = getDeleteItem(index); dispatch(action); }, initList() { const action = getTodoList(); dispatch(action); } } }
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
|