前端(十):使用redux管理数据
react本身能够完成动态数据的监听和更新,如果不是必要可以不适用redux。
安装redux: cnpm install redux --save,或者yarn add redux。
一、react基本用法
redux是独立的用于状态管理的第三方包,它建立状态机来对单项数据进行管理。
上图是个人粗浅的理解。用代码验证一下:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux"; function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case "add":
state.age ++;
return state;
case "dec":
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
const add = {type: "add"}, dec={type: "dec"}; class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
// console.log(store.getState());
const obj = store.getState();
// console.log(obj);
return (
<div>
<h2>this { obj.name } is { obj.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(this.props.add)}>增加一岁</button>
<button style={style} onClick={()=>store.dispatch(this.props.dec)}>减少一岁</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);
因为action必须是个对象,所以只能写成add = {type: "add"}的形式,而不能直接写参数"add"。同样地,在reducer中写switch时将action.type作为参数。
action和state一一对应,要使用action必须要在reducer里声明。
redux没有用state来实现动态数据更新,而是通过props来传递数据,因此在组件内部只能通过props获取store,以及store.getState()获取state。
redux将ReactDOM.render进行了一次封装来设置监听。
redux对数据和组件进行了解耦,因而可以进行文件拆分。
把action写成函数的形式:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux"; const Add = "add", Dec="dec";
function reducer(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
const store = createStore(reducer);
class App extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
const state = store.getState();
return (
<div>
<h2>this { state.name } is { state.age } years old.</h2>
<button style={style} onClick={()=>store.dispatch(add())}>增加一岁</button>
<button style={style} onClick={()=>store.dispatch(dec())}>减少一岁</button>
</div>
)
}
}
function render() {
ReactDOM.render(<App store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);
或者也可以写成原始的:
// reducer.js
const defaultState = {
inputValue: '',
list: [1, 2, 3]
}; // reducer可以接受state,但是无法修改state,所以只能拷贝修改
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);
return newState;
}
if(action.type === 'delete_item'){
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1);
return newState;
}
return state;
}
// store.js
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); export default store;
// ReduxDemo.js
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store'; class ReduxDemo extends React.Component{ constructor(props){
super(props);
this.state = store.getState();
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleInpChange = this.handleInpChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
// 使用subscribe监听函数时一定要事先绑定到this上
store.subscribe(this.handleStoreChange);
} render(){
return (
<Fragment>
<Row gutter={24} size="large">
<Col span={12} offset={4} style={{padding: "0px"}}>
<Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
onChange={this.handleInpChange}
/>
</Col>
<Col span={2} style={{padding:"0px"}}>
<Button
type='primary'
style={{width:"100%", height:"50px"}}
onClick={this.handleBtnClick}
>submit</Button>
</Col>
</Row>
<List
bordered={1}
dataSource={this.state.list}
renderItem={
(item, index) => (
<List.Item column={12}
onClick={this.handleItemDelete.bind(this,index)}
>{item}</List.Item>
)
}
/>
</Fragment>
)
}
handleBtnClick(){
const action = {
type: 'add_item'
};
store.dispatch(action);
} handleInpChange(e) {
const action = {
type: 'change_input_value',
value: e.target.value
};
store.dispatch(action);
}
handleStoreChange(){
this.setState(()=>store.getState());
};
handleItemDelete(index){
const action = {
type: 'delete_item',
value: index
};
store.dispatch(action); // dispatch被监听函数调用,并将action提交给store,store将上一次的数据状态state和本次的action一起交给reducer去执行逻辑处理
}
}
export default ReduxDemo;
或者改为:
// actioncreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item'; export const getInpChangeAction = (value)=>({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = ()=>({
type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
type: DELETE_ITEM,
value: index
});
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM} from './actionCreators' const defaultState = {
inputValue: '',
list: [1, 2, 3]
}; // reducer可以接受state,但是无法修改state,所以只能拷贝修改
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);
return newState;
}
if(action.type === DELETE_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1);
return newState;
}
return state;
}
// store
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); export default store;
// ReduxDemo
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import store from './store';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators'; class ReduxDemo extends React.Component{ constructor(props){
super(props);
this.state = store.getState();
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleInpChange = this.handleInpChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
// 使用subscribe监听函数时一定要事先绑定到this上
store.subscribe(this.handleStoreChange);
} render(){
return (
<Fragment>
<Row gutter={24} size="large">
<Col span={12} offset={4} style={{padding: "0px"}}>
<Input defaultValue='todo info' style={{height:"50px"}} value={this.state.inputValue}
onChange={this.handleInpChange}
/>
</Col>
<Col span={2} style={{padding:"0px"}}>
<Button
type='primary'
style={{width:"100%", height:"50px"}}
onClick={this.handleBtnClick}
>submit</Button>
</Col>
</Row>
<List
bordered={1}
dataSource={this.state.list}
renderItem={
(item, index) => (
<List.Item column={12}
onClick={this.handleItemDelete.bind(this,index)}
>{item}</List.Item>
)
}
/> </Fragment>
)
}
handleBtnClick(){
const action = getAddItemAction();
store.dispatch(action);
}
handleInpChange(e) {
const action = getInpChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(()=>store.getState());
};
handleItemDelete(index){
const action = getDeleteItemAction(index);
store.dispatch(action);
}
}
export default ReduxDemo;
二、UI组件与容器组件拆解
容器组件负责逻辑处理,UI组件只负责页面显示。基于这样的逻辑,可以将上述的ReduxDemo进行拆解。
// 容器组件
import React from 'react';
import store from './store';
import ReduxDemoUI from './ReduxDemoUI';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction } from './actionCreators'; class ReduxDemo extends React.Component{
constructor(props){
super(props);
this.state = store.getState();
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleInpChange = this.handleInpChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
// 使用subscribe监听函数时一定要事先绑定到this上
store.subscribe(this.handleStoreChange);
} render(){
return (
<ReduxDemoUI
inputValue={this.state.inputValue}
list={this.state.list}
handleBtnClick={this.handleBtnClick}
handleInpChange={this.handleInpChange}
handleStoreChange={this.handleStoreChange}
handleItemDelete={this.handleItemDelete}
/>)
}
handleBtnClick(){
const action = getAddItemAction();
store.dispatch(action);
}
handleInpChange(e) {
const action = getInpChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(()=>store.getState());
};
handleItemDelete(index){
const action = getDeleteItemAction(index);
store.dispatch(action);
}
}
export default ReduxDemo;
// UI组件
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd'; class ReduxDemoUI extends React.Component{
render(){
return (
<Fragment>
<Row gutter={24} size="large">
<Col span={12} offset={4} style={{padding: "0px"}}>
<Input defaultValue='todo info' style={{height:"50px"}} value={this.props.inputValue}
onChange={this.props.handleInpChange}
/>
</Col>
<Col span={2} style={{padding:"0px"}}>
<Button
type='primary'
style={{width:"100%", height:"50px"}}
onClick={this.props.handleBtnClick}
>submit</Button>
</Col>
</Row>
<List
bordered={1}
dataSource={this.props.list}
renderItem={
(item, index) => (
<List.Item column={12}
onClick={(index) => {this.props.handleItemDelete(index)}}
>{item}</List.Item>
)
}
/> </Fragment>
)
}
}
export default ReduxDemoUI;
当一个组件只有render函数时,可以改写成无状态组件,无状态组件只是一个函数,因此性能要比UI组件高。
// 将ReduxDemoUI改写
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd'; export const ReduxDemoUI = (props) => {
return (<Fragment>
<Row gutter={24} size="large">
<Col span={12} offset={4} style={{padding: "0px"}}>
<Input defaultValue='todo info' style={{height: "50px"}} value={props.inputValue}
onChange={props.handleInpChange}
/>
</Col>
<Col span={2} style={{padding: "0px"}}>
<Button
type='primary'
style={{width: "100%", height: "50px"}}
onClick={props.handleBtnClick}
>submit</Button>
</Col>
</Row>
<List
bordered={1}
dataSource={props.list}
renderItem={
(item, index) => (
<List.Item column={12}
onClick={() => {
props.handleItemDelete(index)
}}
>{item}</List.Item>
)
}
/>
</Fragment>)};
// ReduxDemo只修改导入方式
// import ReduxDemoUI from './ReduxDemoUI';
import {ReduxDemoUI} from './ReduxDemoUI';
三、请求数据渲染
利用redux+axios发送数据,获取数据并将数据渲染。
// actionCreators
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item'; export const AXIO_LIST = 'axios_list'; // 添加状态 export const getInpChangeAction = (value)=>({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = ()=>({
type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
type: DELETE_ITEM,
value: index
});
// 添加action
export const getAxiosListAction = (list) => ({
type: AXIO_LIST,
list
});
// reducers
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators' 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);
return newState;
}
if(action.type === DELETE_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1);
return newState;
}
// 添加状态处理
if(action.type === AXIO_LIST){
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.list;
return newState;
}
return state;
}
// ReduxDemo import React from 'react';
import store from './store';
import axios from 'axios';
import {ReduxDemoUI} from './ReduxDemoUI';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListAction } from './actionCreators'; class ReduxDemo extends React.Component{
constructor(props){
super(props);
this.state = store.getState();
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleInpChange = this.handleInpChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
// 使用subscribe监听函数时一定要事先绑定到this上
store.subscribe(this.handleStoreChange);
} render(){
return (
<ReduxDemoUI
inputValue={this.state.inputValue}
list={this.state.list}
handleBtnClick={this.handleBtnClick}
handleInpChange={this.handleInpChange}
handleStoreChange={this.handleStoreChange}
handleItemDelete={this.handleItemDelete}
/>)
}
handleBtnClick(){
const action = getAddItemAction();
store.dispatch(action);
}
handleInpChange(e) {
const action = getInpChangeAction(e.target.value);
store.dispatch(action);
}
handleStoreChange(){
this.setState(()=>store.getState());
};
handleItemDelete(index){
const action = getDeleteItemAction(index);
store.dispatch(action);
}
// 发送请求,获取list,并交给store,store将action和list交给reducer处理,然后store在constructor中监听返回
// 然后storec调用store.dispatch将reducer更新后的数据更新到组件的setState中,随后render方法开始渲染
componentDidMount() {
axios({
method: 'get',
url: 'http://localhost:8080/ccts/node/test',
responseType: 'json'
}).then((response) => {
// 请求成功时
const action = getAxiosListAction(response.data.list);
store.dispatch(action);
}).catch((error) => {
console.log(error);
});
}
}
export default ReduxDemo;
store.js和ReduxDemoUI.js保持不变。启动一个后台服务,这里用java起了一个后台来传递list。注意在package.json中设置proxy为"http://localhost:8080"。
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/node")
public class Service {
@RequestMapping(value = {"/test"}, method = {RequestMethod.GET})
@ResponseBody
public String test(HttpServletRequest request){
// consumes = "application/json")
Map<String, ArrayList> map = new HashMap<String, ArrayList>(1);
ArrayList<String> strings = new ArrayList<String>();
strings.add("张三");
strings.add("李四");
strings.add("王二");
strings.add("麻子");
map.put("list", strings);
return JSON.toJSONString(map);
}
}
四、redux异步处理
当一个父组件中有多个子组件时,如果每一个组件发生状态变化,store都要重新渲染一遍。使用异步可以只重新渲染发生状态变化的组件。
安装redux和thunk的中间件redux-thunk:cnpm install redux-thunk --save。引入两个函数,并修改createStore如下,其余代码保持不变:
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));
当然还可以使用compose在控制台添加调试功能,前提是这需要在浏览器中安装redux插件,百度下载redux-devtools.crx,并直接拖动到谷歌浏览器扩展程序页面(这就是安装了)。
import { createStore, applyMiddleware, compose } from "redux";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f
)
);
最终代码如下:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f
)
);
export default store;
thunk支持异步请求,它的action可以是一个函数。为了启用异步请求,对请求进行改写。
thunk中间件指的是store和action之间的中间件。当store.dispatch被执行时,会检测action是一个对象或是一个函数,当是一个函数时,会自动执行这个函数并将结果再交给store。然后和对象那种处理方式一样,store将action交给reducer处理。
// actionCreators添加如下方法
export const getAxiosListThunk = () => {
// 返回一个函数,它可以自动接收一个名为dispatch的方法,处理异步
return (dispatch) => {
axios({
method: 'get',
url: 'http://localhost:8080/ccts/node/test',
responseType: 'json'
}).then((response) => {
// 请求成功时
const action = getAxiosListAction(response.data.list);
dispatch(action); // 只将store.dispatch()改写成了dispatch()
}).catch((error) => {
console.log(error);
});
};
};
// ReduxDemo中修改componnetDidMount
componentDidMount() {
const action = getAxiosListThunk();
store.dispatch(action); // dispatch会自动调用和执行action函数
// axios({
// method: 'get',
// url: 'http://localhost:8080/ccts/node/test',
// responseType: 'json'
// }).then((response) => {
// // 请求成功时
// const action = getAxiosListAction(response.data.list);
// store.dispatch(action);
// }).catch((error) => {
// console.log(error);
// });
}
五、组件间与状态机、状态机与状态机
内层的组件调用dispatch,那么它的父组件一直到最外层组件都需要使用store属性一层一层地传递状态机。
状态机与状态机之间需要使用redux中的combineReducers合并成一个对象。并由dispatch调用的action来查找其对应的reducer一级返回的state。
举个例子:
在src目录下新建index.redux.js文件,建立两个reducer以及各自的action。
// src/index.redux.js
const Add = "add", Dec="dec";
export function reducerOne(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
state.age ++;
if (state.age > 5005){
state.name = "old monkey";
}
return state;
case Dec:
if (state.age <= 4995){
state.name = "small monkey";
}
state.age --;
return state;
default:
return state;
}
}
export function add() {
return {type: Add}
}
export function dec() {
return {type: Dec}
} export function reducerTwo(state="孙悟空", action){
if(action.type >= 5005){
state = "斗战胜佛";
}else if(action.type <= 4995){
state = "小猕猴";
}
return state;
}
export function monkey(number) {
return {type: number}
}
在src/index.js中写入如下代码:
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'redux-thunk'; import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux"; const reducer = combineReducers({reducerOne, reducerTwo});
const store = createStore(reducer, applyMiddleware(thunk)); class Nick extends React.Component{
render(){
const store = this.props.store;
const state = store.getState();
return <h2 >Now this monkey is so-called { state.reducerTwo }</h2>
}
}
class Monkey extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
const store = this.props.store;
const state = store.getState();
const name = state.reducerOne.name, age = state.reducerOne.age;
return (
<div>
<h2>this { name } is { age } years old.</h2>
<button style={style} onClick={()=>{store.dispatch(add()); store.dispatch(monkey(age))}}>增加一岁</button>
<button style={style} onClick={()=>{store.dispatch(dec()); store.dispatch(monkey(age))}}>减少一岁</button>
<Nick store={store} />
</div>
)
}
}
function render() {
ReactDOM.render(<Monkey store={store} add={ add } dec={ dec } />, document.getElementById('root'));
}
render();
store.subscribe(render);
在Monkey组件中,onClick同时触发两个状态机reducerOne,reducerTwo。Nick组件接收点击事件触发的状态修改之后的store,并从中获取相应的字段进行渲染。在这里需要注意:
1.使用combineReducers将Monkey复合组件涉及到的状态机进行合并。
2.多个reducer时,store.getState()获取的是一个对象。可以通过reducer名来获取对应reducer的state。
3.store和action必须一级一级的往下传递。
六、使用react-redux(以Monkey和Nick为例)
react-redux简化了redux在组件的依次传递问题。
react-redux有两个核心api:Provider和connect。Provider声明所有被其包裹的子组件都可以从其store属性中获取数据,而不必逐次传递。connect允许每个组件跟Provider创建连接来获取数据。
这里对上面的代码进行修改:
- src/index.redux.js文件内容如下:reducer中的state此时必须重新定义,不能直接再返回原来的state。然后微调了一下显示的数据。
// src/index.redux.js
const Add = "add", Dec="dec";
export function reducerOne(state={name: "monkey", age: 5000}, action){
switch (action.type){
case Add:
if (state.age >= 5004){
state.name = "old monkey";
}else if(state.age > 4994 && state.age < 5004){
state.name = "monkey";
}
return {...state, age: state.age+1};
case Dec:
if (state.age <= 4996){
state.name = "small monkey";
}else if(state.age > 4996 && state.age <= 5005){
state.name = "monkey";
}
return {...state, age: state.age-1};
default:
return state;
}
}
export function add() {
return {type: Add}
}
export function dec() {
return {type: Dec}
}
export function reducerTwo(state="孙悟空", action){
if(action.type >= 5004){
return "斗战胜佛";
}else if(action.type <= 4996){
return "小猕猴";
}
return state;
}
export function monkey(number) {
return {type: number}
}
- src/index.js内容如下:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'redux-thunk';
import { Provider, connect } from 'react-redux'; import { reducerOne, reducerTwo, add, dec, monkey } from "./index.redux"; const reducer = combineReducers({reducerOne, reducerTwo});
const store = createStore(reducer, applyMiddleware(thunk)); class Nick extends React.Component{
render(){
return <h2 >Now this monkey is so-called { this.props.nick }</h2>
}
}
class Monkey extends React.Component{
render (){
const style={
display: "inline-block",
width: "150px",
height: "40px",
backgroundColor: "rgb(173, 173, 173)",
color: "white",
marginRight: "20px"
};
return (
<div>
<h2>this { this.props.reducerOne.name } is { this.props.reducerOne.age } years old.</h2>
<button style={style} onClick={()=>{this.props.add(); this.props.monkey(this.props.reducerOne.age)}}>增加一岁</button>
<button style={style} onClick={()=>{this.props.dec();this.props.monkey(this.props.reducerOne.age)}}>减少一岁</button>
<Nick nick={ this.props.reducerTwo } />
</div>
)
}
}
const mapStateProps = state=>{
return { reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }
};
const actionCreator = { add, dec, monkey };
Monkey = connect(mapStateProps, actionCreator)(Monkey); // 其实是加了一层装饰器 function render() {
ReactDOM.render(
<Provider store={store}>
<Monkey/>
</Provider>
, document.getElementById('root'));
}
render();
首先,从react-redux中引入Provider,用来包装app Monkey,然后将store传递给Provider即可。
其次,用装饰器对app Monkey进行装饰,并传递进去两个参数,第一个参数是组件所需要的reducer中的状态(这里把两个reducer的状态全部写了进来),第二个参数是action指令。connect函数会根据将reducer和action一一对应起来,所以后面直接用action函数即可。
再次,onClick绑定action只需要写成onClick={this.props.action}即可,这里需要变更两个状态,所以用箭头函数包裹了一下。
connect的这种写法看起来不爽,可以用装饰器的形式写。但是需要做一下babel配置:
# 1.生成配置文件
cnpm run eject
# 2.安装插件
cnpm install babel-plugin-transform-decorators-legacy --save
# 3.在package.json中的"babel"中做如下配置:
src/package.json
"babel": {
"presets": [
"react-app"
],
"plugins":[
"transform-decorators-legacy"
]
},
此时将装饰器的那三行代码改为一行,写在Monkey类上面即可。
@connect(state=>({ reducerOne: state.reducerOne, reducerTwo: state.reducerTwo }), { add, dec, monkey })
class Monkey extends React.Component{
...
}
备注:react-16.6.3,babel-7.0以上安装需要做如下配置。
1.npm run eject
2.安装插件
cnpm install babel-plugin-transform-decorators-legacy --save
cnpm install --save-dev babel-preset-env
cnpm install --save-dev babel-plugin-transform-class-properties
cnpm install --save-dev @babel/plugin-transform-react-jsx
cnpm install --save-dev @babel/plugin-transform-react-jsx-source
cnpm install --save-dev @babel/plugin-transform-react-jsx-self
cnpm install --save-dev @babel/plugin-proposal-decorators
3.修改package.json
"babel": {
"presets": [
"react-app"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
七、使用React-Redux(以ReduxDemo为例)
保持reducers.js、store.js、actionCreators.js不变,改写ReduxDemo.js和index.js,删除ReduxDemoUI.js。
// reducers.js
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, AXIO_LIST} from './actionCreators' 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);
return newState;
}
if(action.type === DELETE_ITEM){
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1);
return newState;
}
if(action.type === AXIO_LIST){
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.list;
return newState;
}
return state;
}
// store.js
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension?window.devToolsExtension():f=>f
)
);
export default store;
import axios from "axios"; export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item'; export const AXIO_LIST = 'axios_list'; export const getInpChangeAction = (value)=>({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = ()=>({
type: ADD_ITEM,
});
export const getDeleteItemAction = (index)=>({
type: DELETE_ITEM,
value: index
}); export const getAxiosListAction = (list) => ({
type: AXIO_LIST,
list
}); export const getAxiosListThunk = () => {
// 返回一个函数,它可以自动接收一个名为dispatch的方法,处理异步
return (dispatch) => {
axios({
method: 'get',
url: 'http://localhost:8080/ccts/node/test',
responseType: 'json'
}).then((response) => {
// 请求成功时
const action = getAxiosListAction(response.data.list);
dispatch(action);
}).catch((error) => {
console.log(error);
});
};
};
// ReduxDemo.js
import React, {Fragment} from 'react';
import 'antd/dist/antd.css';
import { Input, Button, Row, Col, List } from 'antd';
import { connect } from 'react-redux';
import {getAddItemAction, getDeleteItemAction, getInpChangeAction, getAxiosListThunk } from './actionCreators'; class ReduxDemo extends React.Component{
render(){
return (
<Fragment>
<Row gutter={24} size="large">
<Col span={12} offset={4} style={{padding: "0px"}}>
<Input defaultValue='todo info' style={{height: "50px"}} value={this.props.inputValue}
onChange={(e) => {this.props.getInpChangeAction(e.target.value)}}
/>
</Col>
<Col span={2} style={{padding: "0px"}}>
<Button
type='primary'
style={{width: "100%", height: "50px"}}
onClick={() => {this.props.getAddItemAction()}}
>submit</Button>
</Col>
</Row>
<List
bordered={1}
dataSource={this.props.list}
renderItem={
(item, index) => (
<List.Item column={12}
onClick={() => {
this.props.getDeleteItemAction(index)
}}
>{item}</List.Item>
)
}
/>
</Fragment>)
}
componentDidMount() {
this.props.getAxiosListThunk();
}
} // 接收一个固定的state参数,并返回一个对象
// connect -> props和store做连接,将store中的数据映射到props属性中去
const mapStateProps = (state) => {
return { inputValue: state.inputValue, list: state.list }
};
// 同样地,将store中的action映射到props属性中去
const mapActionProps = {
getAddItemAction,
getDeleteItemAction,
getInpChangeAction,
getAxiosListThunk
}; ReduxDemo = connect(mapStateProps, mapActionProps)(ReduxDemo);
export default ReduxDemo;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'; import store from './Boom09/store';
import ReduxDemo from './Boom09/ReduxDemo'; const App = (
<Provider store={store}>
<ReduxDemo />
</Provider>
); ReactDOM.render(App, document.getElementById('root'));
四、react-router-dom
react-router-dom是专门用于处理路由的组件。
BrowserRouter用以包裹app,它会在this.props添加一些环境参数,这非常用。
Route用于将路由和组件绑定,一个路由对应一个页面(组件),或者Link。其重要的两个属性为path和component,path可以接收对象,也可以接收一个url字符串。
Link用于将链接和Route进行绑定。
Switch用于当前路由下的所有子路由,一般和Redirect配合使用,用于重定向那些未定义的子路由。
Redirect:重定向。其重要的属性为to,只能指向一个url字符串。
用例一: Route + Componnet + Redirect
1.建立两个路由指向两个组件:Login和Home, 以及一个用户登录状态的reducer。
2.进入时首先重定向到Home页面.Home组件判断用户登录状态,登录则打印登录信息并提供注销按钮,点击按钮会重定向到Login页面;未登录则直接重定向到Login页面。
3.用户登录则重定向到Home页面,未登录则打印登录提示并提供登录按钮,点击按钮会重定向到Home页面。
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import {BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; import { reducer } from "./reducer";
import { Login } from "./Login";
import { Home } from "./Home"; const store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
(<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/login" component={ Login } />
<Route path="/monkey" component={ Home } />
<Redirect to={{ pathname: "/monkey" }}/>
</Switch>
</BrowserRouter> </Provider>),
document.getElementById('root')
);
// src/Home.js
import React from "react";
import {connect} from 'react-redux';
import { Redirect } from 'react-router-dom';
import {logout} from "./reducer"; @connect(state=>state, {logout})
class Home extends React.Component{
render(){
const app = (
<div>
<h2>这是home界面</h2>
<button onClick={this.props.logout}>点击注销</button>
</div>
);
return this.props.isLogin ? app : <Redirect to={{ pathname: "/login"}} />
}
}
export { Home }
// src/Login.js
import React from 'react';
import {connect} from 'react-redux';
import {Redirect} from 'react-router-dom';
import {login} from "./reducer"; @connect(state=>state, {login})
class Login extends React.Component{
render(){
console.log(this.props);
const app = (
<div>
<h2>请先登录!</h2>
<button onClick={this.props.login}>点击登录</button>
</div>
);
return this.props.isLogin ? <Redirect to={{pathname: "/home"}} /> : app
}
}
export { Login }
// src/reducer.js
// 用户登录状态
const LOGIN = "login";
const LOGOUT = "logout"; export function reducer(state={isLogin: false, user: "Li"}, action) {
switch (action.type){
case LOGIN:
return {...state, isLogin:true};
case LOGOUT:
return {...state, isLogin:false};
default:
return state
}
} export function login() {
return {type: LOGIN}
}
export function logout() {
return {type: LOGOUT}
}
用例二:Route + Component + Link
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, Link } from 'react-router-dom'; function LinkOne(){
return <h2>我是LineOne组件,被Route绑定给了一个Link链接</h2>
}
function LinkTwo() {
return <h2>我是LineThree组件,被Route绑定给了一个Link链接</h2>
}
function LinkThree(){
return <h2>我是LineThree组件,被Route绑定给了一个Link链接</h2>
}
function Test(){
return <h2>这是无效的路由,你不要搞事情!</h2>
} ReactDOM.render(
(<BrowserRouter>
<div>
<ul>
<li><Link to="/">LinkOne</Link></li>
<li><Link to="/two">LinkTwo</Link></li>
<li><Link to="/three">LinkThree</Link></li>
</ul>
<Switch>
<Route path="/" exact component={ LinkOne } />
<Route path="/two" component={ LinkTwo } />
<Route path="/three" component={ LinkThree } />
<Route path='/:location' component={ Test } />
</Switch>
</div>
</BrowserRouter>),
document.getElementById('root')
);
前端(十):使用redux管理数据的更多相关文章
- 使用Redux管理你的React应用
因为redux和react的版本更新的比较频繁,博客园这里用的redux版本是1.0.1,如果你关心最新版本的使用技巧,欢迎来我的Github查看(https://github.com/matthew ...
- VSTO学习笔记(十四)Excel数据透视表与PowerPivot
原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...
- Redux管理你的React应用
使用Redux管理你的React应用 因为redux和react的版本更新的比较频繁,博客园这里用的redux版本是1.0.1,如果你关心最新版本的使用技巧,欢迎来我的Github查看(https ...
- Python Django CMDB项目实战之-2创建APP、建模(models.py)、数据库同步、高级URL、前端页面展示数据库中数据
基于之前的项目代码来编写 Python Django CMDB项目实战之-1如何开启一个Django-并设置base页index页文章页面 现在我们修改一个文章列表是从数据库中获取数据, 下面我们就需 ...
- FreeSql (十五)查询数据
FreeSql在查询数据下足了功能,链式查询语法.多表查询.表达式函数支持得非常到位. IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnect ...
- PersistGate轻松几步让Redux实现数据持久化
在开发的过程中,数据用redux管理,觉得希望将数据持久化保存,也就是说当用户下一次打开app或网站的时候,我们希望浏览器/APP自动加载出上次的数据,怎么办?有没有一个
- 使用Redux管理React数据流要点浅析
在图中,使用Redux管理React数据流的过程如图所示,Store作为唯一的state树,管理所有组件的state.组件所有的行为通过Actions来触发,然后Action更新Store中的stat ...
- 循序渐进VUE+Element 前端应用开发(27)--- 数据表的动态表单设计和数据存储
在我们一些系统里面,有时候会需要一些让用户自定义的数据信息,一般这些可以使用扩展JSON进行存储,不过每个业务表的显示项目可能不一样,因此需要根据不同的表单进行设计,然后进行对应的数据存储.本篇随笔结 ...
- WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)
原文:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话)]]在.NE ...
随机推荐
- BZOJ 1001--[BeiJing2006]狼抓兔子(最短路&对偶图)
1001: [BeiJing2006]狼抓兔子 Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 29035 Solved: 7604 Descript ...
- webpack快速入门——实战技巧:开发和生产并行设置
package.json中,devDependencies和dependencies是不同的 devDependencies:开发依赖 dependencies:生产依赖(线上) 1.安装生产环境的依 ...
- MongoDB系统CentOS 7.1 crash的排障过程
[作者] 王栋:携程技术保障中心数据库专家,对数据库疑难问题的排查和数据库自动化智能化运维工具的开发有强烈的兴趣. [问题描述] 最近我们有多台MongoDB的服务器CentOS 7.1系统发生了cr ...
- php内核为变量的值分配内存的几个宏
在php5.3之前,为某变量分配内存是用宏 MAKE_STD_ZVAL; 737 #define MAKE_STD_ZVAL(zv) \ # /Zend/zend.h738 ALLOC_ZVAL(zv ...
- AngularJS入门之数据验证
AngularJS自带了对表单或控件的输入数据进行验证的功能,对于Html5的基础控件均有内建的验证器,以下列举了所有支持的验证类型: email max maxlength min minlengt ...
- h5聊天室web端(仿微博、微信)|h5仿微信网页端|仿微信界面弹窗
这段时间一直在着手h5开发手机端聊天系统——html5仿微信聊天室,最近又在原先基础上开发了一个仿微信.微博网页web版聊天系统,使用到了HTML5+css3+jQuery+wcpop等技术开发,弹窗 ...
- (转) mysql之status和variables区别及用法详解
原文:http://blog.csdn.net/andyzhaojianhui/article/details/50052117
- Android 开发服务类 02_NewsListServlet
Servlet implementation class NewsListServlet package com.wangjialin.server.xml; import java.io.IOExc ...
- 可视化Echarts的使用示例
1.照着官方文档简单做两个图表,感受一下. <!DOCTYPE html> <html> <head> <meta charset="UTF-8&q ...
- saltstack快速入门
SALTSTACK是什么? Salt是一种和以往不同的基础设施管理方法,它是建立在大规模系统高速通讯能力可以大幅提升的想法上.这种方法使得Salt成为一个强大的能够解决基础设施中许多特定问题的多任务系 ...