React-Redux
- React-Redux 是 Redux 的官方 React 绑定库。
- React-Redux 能够使React组件从Redux store中读取数据,并且向 store 分发 actions 以更新数据。
- React-Redux 并不是 Redux 内置,需要单独安装。
- React-Redux 一般会和 Redux 结合一起使用。
一、安装redux、react-redux
npm install redux
npm install react-redux
二、Provider 和 connect
Provider:它是react-redux 提供的一个 React 组件,作用是把state传给它的所有子组件,也就是说 当你用Provider传入数据后 ,下面的所有子组件都可以共享数据,十分的方便。
Provider的使用方法是:用Provider组件包裹在最外层的组件
<Provider store={ store }>
<App />
</Provider>
注意:一定是在Provider中传store
connect:它是一个高阶组件 所谓高阶组件就是给它传入一个组件,它会返回新的加工后的组件,connect 方法有四个参数([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])后面两个参数可以不写,不写的话它是有默认值的。主要关注前两个参数: mapStateToProps和 mapDispatchToProps;
Ø mapStateToProps:如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。
Ø mapDispatchToProps:如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中dispatch方法会将 actioncreator 的返回值作为参数执行。这些属性会被合并到组件的 props 中。
connect的使用方法是:把指定的state和指定的action与React组件连接起来,后面括号里面写UI组件名
connect(mapStateToProps,mapDispatchToProps)(TbItem)
三、示例:
例1:利用react-redux实现计数器
1、目录结构
2、store.js
import {createStore} from "redux";
export const ADD = 'ADD';
export const MINUS = 'MINUS';
function reducer (state= {count:0},action){
console.log('Action:',action);
switch (action.type) {
case ADD:
return { count: state.count + 1}
case MINUS:
return { count: state.count - 1}
default:
return state
}
}
let store = createStore(reducer);
export default store;
3、action.js
function add() {
return {
type: 'ADD'
}
}
function minus() {
return {
type: 'MINUS'
}
}
const action = { add,minus }
export default action;
4、Counter1.js
import React,{ Component } from "react";
import { connect } from "react-redux";
import action from "../actions/action";
class Counter1 extends Component {
render() {
return (
<div>
<p>{ this.props.count }</p>
<button onClick={this.props.add }>Count++</button>
<button onClick={ this.props.minus}>count--</button>
</div>
)
}
}
const mapStateToProps = state=> state
const mapDispatchToProps = {
...action
}
export default connect(mapStateToProps,mapDispatchToProps)(Counter1);
5、App.js
import logo from './logo.svg';
import './App.css';
import Counter1 from "./components/Counter1";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Counter1 />
</header>
</div>
);
}
export default App;
6、index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from "react-redux";
import store from "./store/store";
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);
action:行为 它是一个对象 里面必有type来指定其类型 这个类型可以理解为你要做什么,reducer要根据action的type来返回不同的state 每个项目有且可以有多个action。它是 store 数据的唯一来源。通过 store.dispatch() 将 action 传到 store。记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
reducer: 可以理解为一个专门处理state的工厂,给它一个旧数据它会根据不同action.type返回新的数据,每个项目有且可以有多个reducer。reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的。
store: store本质上是一个状态树,保存了所有对象的状态。任何UI组件都能直接的从store访问特定对象的状态。每个项目有且只能有一个store。
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
例2:多组件共享store
(1)constants.js(定义公共方法名)
export const ADD_NUMBER = 'ADD_NUMBER'
export const SUB_NUMBER = 'SUB_NUMBER'
(2)actionCreators.js(专门存放方法名)
import {
ADD_NUMBER,
SUB_NUMBER
} from "./constants";
export const addAction = num => ({
type: ADD_NUMBER,
num
})
export const subAction = num => ({
type: SUB_NUMBER,
num
})
(3)reducer.js(存放事件)
import {
ADD_NUMBER,
SUB_NUMBER
} from "../action/constants";
const deflateState = {
counter: 0
}
function reducer(state = deflateState,action){
switch (action.type){
case ADD_NUMBER:
return {...state,counter: state.counter + action.num }
case SUB_NUMBER:
return {...state,counter: state.counter - action.num }
default:
return state;
}
}
export default reducer;
(4)store.js文件
import { createStore } from "redux";
import reducer from "../reducer/reducer";
const store = createStore(reducer);
export default store;
(5)home.js(Home组件)
import React,{ Component } from "react";
import store from "../redux/store/store";
import { addAction } from "../redux/action/actionCreators";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
//订阅
componentDidMount() {
this.unSubscribe = store.subscribe(()=>{
this.setState({
counter: store.getState().counter
})
})
}
//取消订阅
componentWillUnmount() {
this.unSubscribe();
}
render() {
return (
<div>
<h2>Home</h2>
<h3>当前: { this.state.counter }</h3>
<button onClick={ event => this.increment() }>+1</button>
<button onClick={ event => this.addNumber(5) }>+5</button>
</div>
)
}
increment(){
store.dispatch(addAction(1));
}
addNumber(num){
store.dispatch(addAction(num));
}
}
(7)about.js(About组件)
import React,{ Component } from "react";
import store from "../redux/store/store";
import { addAction } from "../redux/action/actionCreators";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
counter: store.getState().counter
}
}
//订阅
componentDidMount() {
this.unSubscribe = store.subscribe(()=>{
this.setState({
counter: store.getState().counter
})
})
}
//取消订阅
componentWillUnmount() {
this.unSubscribe();
}
render() {
return (
<div>
<h2>Home</h2>
<h3>当前: { this.state.counter }</h3>
<button onClick={ event => this.increment() }>+1</button>
<button onClick={ event => this.addNumber(5) }>+5</button>
</div>
)
}
increment(){
store.dispatch(addAction(1));
}
addNumber(num){
store.dispatch(addAction(num));
}
}
(8)App.js
import logo from './logo.svg';
import './App.css';
import Home from "./components/home";
import About from "./components/about";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Home />
<About/>
</header>
</div>
);
}
export default App;
(9)index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import store from "./redux/store/store";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
例3、合并多个reducer
(1)目录结构
(2)action文件
2.1、changeAge_action.js
export const CLICK_ADD = 'CLICK_ADD';
export const CLICK_SUB = 'CLICK_SUB';
export function clickAdd() {
return {
type: CLICK_ADD
}
}
export function clickSub() {
return {
type: CLICK_SUB
}
}
2.2、inputInfo.js
export const INPUT_INFO = 'INPUT_INFO';
export function inputInfo(name){
return {
type: INPUT_INFO,
name
}
}
(3)reducer文件
3.1、changeAge_reducer.js
import { CLICK_ADD,CLICK_SUB } from "../actions/changeAge_action";
function initialState() {
return {
age: 20
};
}
function changeAge(state=initialState(),action) {
switch (action.type) {
case CLICK_ADD: {
return {
...state,
age: state.age + 1
}
}
case CLICK_SUB: {
return {
...state,
age: state.age - 1
}
}
default:
return state;
}
}
export default changeAge;
3.2、inputInfo_reducer.js
import { INPUT_INFO } from "../actions/inputInfo_action";
function initialState() {
return {
name: "ZhangSan"
}
}
function inputInfo(state= initialState(),action) {
switch (action.type) {
case INPUT_INFO: {
return {
...state,
name: action.name
}
}
default:
return state;
}
}
export default inputInfo;
3.3、rootReducer.js
(4)store.js文件
import { createStore } from "redux";
import rootReducer from "../reducer/rootReducer";
const store = createStore(rootReducer);
export default store;
(5)组件
5.1、Name组件(name.js)
import React,{ Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
class Name extends Component {
render() {
const { name } = this.props
return (
<div>
My name is <i>{ name }</i>
</div>
)
}
}
Name.propTypes = {
name: PropTypes.string.isRequired
}
const mapStateToProps = (state)=> {
return {
name: state.Name_store.name
}
}
export default connect(mapStateToProps)(Name);
5.2、Age组件(age.js)
import React,{ Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {clickAdd,clickSub } from "../actions/changeAge_action";
class Age extends Component{
constructor(props) {
super(props);
this.clickAdd = this.clickAdd.bind(this);
this.clickSub = this.clickSub.bind(this);
}
render() {
const { age } = this.props
return (
<div>
I am <i>{ age } </i>years old this year
<button onClick={this.clickAdd }>Add Age</button>
<button onClick={this.clickSub }>Sub Age</button>
</div>
)
}
clickAdd(event) {
this.props.dispatch(clickAdd());
}
clickSub(event) {
this.props.dispatch(clickSub());
}
}
Age.propTypes = {
age: PropTypes.number.isRequired
}
const mapStateToProps = (state) => {
return {
age: state.Age_store.age
}
}
export default connect(mapStateToProps)(Age);
5.3、Form组件(form.js)
import React,{Component} from "react";
import {connect} from "react-redux";
import { inputInfo } from "../actions/inputInfo_action";
class Form extends Component {
constructor(props) {
super(props);
this.clickSubmit = this.clickSubmit.bind(this);
}
render() {
return (
<div>
<input type={"text"} placeholder={"name"} id={"nameInput"}/>
<button onClick={ this.clickSubmit }>提交</button>
</div>
)
}
clickSubmit(event) {
let name = document.getElementById('nameInput').value;
console.log(name);
this.props.dispatch(inputInfo(name));
}
}
export default connect()(Form);
(6)App.js
import logo from './logo.svg';
import './App.css';
import Name from "./redux/components/name";
import Age from "./redux/components/age";
import Form from "./redux/components/form";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Name />
<br/>
<Age />
<br/>
<Form />
</header>
</div>
);
}
export default App;
(7)index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from "react-redux";
import store from "./redux/store/store";
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);