Redux相关 && React和Redux结合使用

Redux相关

随着应用程序的规模和范围的增长,管理共享数据变得很困难。Redux被定义为“JavaScript应用程序的可预测state的容器”,有助于确保应用程序可预测地工作,更易于测试。接下来我们一起看看,Redux存储、操作、缩减器、中间件管理整个应用程序数据。

Redux是一个状态管理框架,可以与许多不同的web技术一起使用,包括React。在Redux中,只有一个状态对象负责应用程序的整个state。这意味着,如果有一个包含10个组件的React应用程序,并且每个组件都有自己的本地state,则应用程序的整个state将由Redux存储中的单个状态对象定义。当涉及到应用程序state时,Redux存储(Redux store)是唯一的真值来源。这也意味着,任何时候我们的应用程序的任何部分想要更新state,它都必须通过Redux store进行更新。单向数据流使我们更容易在应用程序中跟踪状态管理。

Redux store是一个保存和管理应用程序state的对象。Redux对象上有一个名为createStore()的方法,用于创建Redux storecreateStore()方法将reducer函数作为必需参数。下面声明一个“store”变量并为其赋上createStore()方法,将reducer作为参数传入:

const reducer = (state = 5) => {
  return state;
}
let store= Redux.createStore(reducer);

Redux store对象提供了几种方法,允许我们与它进行交互。如,可以使用getState()方法检索Redux store对象中保存的当前state。上面的例子可以更简洁地重写:

const store = Redux.createStore(
  (state = 5) => state
);
/*使用store.getState()从store中检索state,并将其分配给一个新变量currentState*/
let currentState=store.getState();

由于Redux是一个状态管理框架,更新state是其核心任务之一。在Redux中,所有state更新都由发送action触发。一个动作(action )只是一个JavaScript对象,它包含有关已发生的动作事件的信息Redux store接收这些动作对象,然后相应地更新其state。有时,Redux动作也会携带一些数据。例如,用户登录后,该动作会携带用户名。虽然数据是可选的,但action必须带有“type”属性,该属性指定发生的动作的“type”。将Redux动作视为通信员,通信员将应用程序中发生的事件信息传递到Redux store,然后,store根据发生的动作进行更新state的业务。

编写Redux action就像定义一个带有type属性的对象一样简单:

// 定义一个action:
let action={
  type:'LOGIN'
}
/*声明一个对象“action”,并将属性“type”设置为字符串“LOGIN”*/

创建action后,下一步是将action发送到Redux store,以便它可以更新其state。 这一步通过定义action creator来完成,action creator只是一个返回action的JavaScript函数,即action creator创建表示动作事件的对象:

//定义action:
const action = {
  type: 'LOGIN'
}
//定义action creator:
function actionCreator(){
  return action;
}

dispatch方法用于将action发送到Redux store。调用store.dispatch()并传递从action creator返回的值,将action发送回store。即action creator返回一个带有type属性的发生特定动作的对象,然后,store.dispatch()方法将action对象发送到Redux store。在以上示例代码的基础上,下面2行代码是等效的,都发送了type为LOGIN的动作:

store.dispatch(actionCreator());
store.dispatch({ type: 'LOGIN' });

看个具体例子:

//定义Redux store
const store = Redux.createStore(
  (state = {login: false}) => state
);
//定义action creator
const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};
// 把loginAction()创建的action发送到Redux store:
store.dispatch(loginAction());

在创建并发送action之后,Redux store需要知道如何响应该action,这时reducer函数就起作用了。Redux中的Reducer负责响应action而进行的state修改。reducer接受state和action作为参数,总是返回一个新的state,这是reducer的唯一职能。它没有副作用——从不调用API端点,也不会有任何隐藏的惊喜,reducer只是一个纯函数,它接受state和action,然后返回新的state。

Redux的另一个关键原则是state是只读的。换句话说,reducer函数必须始终返回state的新副本,而不能直接修改state。Redux并没有强制执行state不可变性,但是,我们有责任在reducer函数的代码中强制执行它。

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
//注意,当前state和发送的action已经传给reducer了,所以可以使用action.type直接访问action的type。 //当action的type属性值为'LOGIN'时,返回将login设置为true的新state对象 if(action.type==='LOGIN') { return {login:true};//记住reducer返回的是新state,而state是一个包含键值对的对象!! } else { return state; } }; const store = Redux.createStore(reducer); const loginAction = () => { return { type: 'LOGIN' } };

我们可以让Redux store处理action的多个type。通过在reducer中使用JavaScript 的switch语句来响应不同的action事件。这是编写Redux reducer的标准模式,switch语句切换action。下例通过reducer函数处理多个身份验证action:

const defaultState = {
  authenticated: false
};
//reducer函数
const authReducer = (state = defaultState, action) => {
  switch(action.type){
    case 'LOGIN':
    return {authenticated: true};
    case 'LOGOUT':
    return {authenticated: false};
    default:
    return state;
  }
};
//Redux store
const store = Redux.createStore(authReducer);
//用户登录的action
const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};
//用户注销的action
const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

使用Redux时的一个常见做法是将action的type指定为只读常量,然后在使用这些常量的任何地方引用这些常量:

//Redux通常习惯用大写字母写常量。
const LOGIN='LOGIN';
const LOGOUT='LOGOUT';
const defaultState = {
  authenticated: false
};

const authReducer = (state = defaultState, action) => {

  switch (action.type) {
    case LOGIN: 
      return {
        authenticated: true
      }
    case LOGOUT: 
      return {
        authenticated: false
      }

    default:
      return state;

  }

};

const store = Redux.createStore(authReducer);

const loginUser = () => {
  return {
    type: LOGIN
  }
};

const logoutUser = () => {
  return {
    type: LOGOUT
  }
};

store.subscribe()允许我们向store订阅侦听器函数,store.subscribe()接受一个我们写的函数作为参数,每当针对store发送action时,都会调用这个函数。此方法的一个简单用途是向store订阅一个函数,该函数只需在每次收到action并更新store时记录一条消息。看个例子:

const ADD = 'ADD';

const reducer = (state = 0, action) => {
  switch(action.type) {
    case ADD:
      return state + 1;
    default:
      return state;
  }
};

const store = Redux.createStore(reducer);
//全局变量count:
let count = 0;
/*编写一个回调函数,将此函数传递给store.subscribe()方法,使得每当store接收到一个action时就递增全局变量count*/
function sum(){
count+=1;
};
/*每当store接收到一个action时,subscribe方法就会执行sum函数一次!!!*/
store.subscribe(sum);

store.dispatch({type: ADD});
console.log(count);//1
store.dispatch({type: ADD});
console.log(count);//2
store.dispatch({type: ADD});
console.log(count);//3
store.dispatch({type: ADD});
console.log(count);//4
/*连续调用store.dispatch()方法4次,每次都直接传入一个action对象:{type: ADD}*/

记住Redux的第一个原则:所有应用程序的state都保存在store的state对象中。因此,Redux提供了reducer组合,作为复杂state模型的解决方案。即定义多个reducer来处理应用程序的state的不同部分,然后将这些reducer组合到一个根reducer中,最后将根reducer传递到Redux createStore()方法中。

为了让我们将多个reducer组合在一起,Redux提供了combineReducers()方法。此方法接受对象作为参数,此对象中定义了将键与指定的reducer函数联系到一起的属性。键名会被Redux用作相关的state段的名称。通常,当应用程序的每个state在某种程度上不同或唯一时,分别为它们创建一个reducer是一个很好的做法。例如,在具有用户身份验证的“note-taking”应用程序中,一个reducer处理身份验证,而另一个reducer则处理用户提交的文本和笔记,对于这样的应用程序,我们可以这样编写combineReducers()方法:

const rootReducer = Redux.combineReducers({
  auth: authenticationReducer,
  notes: notesReducer
});

上例中,notes键将包涵与笔记相关、由notesReducer处理的所有state。如此即可组合多个reducer以管理更复杂的应用程序state。本例中,Redux store中保存的state将会是一个包含“auth”和“notes”属性的对象。

再看个例子:

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const counterReducer = (state = 0, action) => {
  switch(action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
};

const LOGIN = 'LOGIN';
const LOGOUT = 'LOGOUT';

const authReducer = (state = {authenticated: false}, action) => {
  switch(action.type) {
    case LOGIN:
      return {
        authenticated: true
      }
    case LOGOUT:
      return {
        authenticated: false
      }
    default:
      return state;
  }
};
//通过Redux.combineReducers()将子reducer函数结合起来,定义根reducer
const rootReducer =Redux.combineReducers({
  count:counterReducer,
  auth:authReducer
});
//将根reducer传给Redux.createStore()方法,创建Redux store
const store = Redux.createStore(rootReducer);

发送到Redux store的action数据不止限于type,还可以发送特定数据:

const ADD_NOTE = 'ADD_NOTE';

const notesReducer = (state = 'Initial State', action) => {
  switch(action.type) {
    case ADD_NOTE:
      return action.text;//返回所传入的action的text属性值作为新的state
    default:
      return state;
  }
};

const addNoteText = (note) => {
  //当调用action创建器时,将传入可以供此对象访问的note,text属性就有了值。
  return {
    type:ADD_NOTE,
    text:note
  }
};
//Redux store
const store = Redux.createStore(notesReducer);

console.log(store.getState());//Initial State
store.dispatch(addNoteText('Hello!'));//向Redux store发送action(调用action创建器)
console.log(store.getState());//Hello!

异步操作(asynchronous action)是web开发中不可避免的一部分。有时我们需要在Redux应用程序中调用异步端点,那么如何处理这类请求呢?Redux有一个专门为此设计的中间件(middleware ),称为Redux Thunk中间件。下面简要说明如何在Redux中使用此功能。

要使用Redux Thunk中间件,可以将其作为参数传递给Redux.applyMiddleware(),然后,Redux.applyMiddleware()可以作为createStore()函数的第二个可选参数。然后,想要创建异步action,就要在action创建器(action creator)中返回一个函数,该函数将dispatch作为参数。在此函数中,我们可以发送action并执行异步请求。下面的例子通过调用setTimeout()模拟异步请求,通常在发起任何异步行为之前发送一个action,以便应用程序的state知道某些数据正被请求(例如state可能会显示一个加载图标)。然后,一旦我们接收到数据,就可以发送另一个action,此action携带了作为装备的数据以及标志action已完成的信息。记住,要创建异步action,是将dispatch作为参数传递给action创建器,然后将action直接传递给dispatch,剩下的事情中间件会处理。

const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'

const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }

const handleAsync = () => {
  return function(dispatch) {
    //发送请求的action
    dispatch(requestingData());
    //使用setTimeout()模拟API调用
    setTimeout(function() {
      let data = {
        users: ['Jeff', 'William', 'Alice']
      }
    //发送收到数据的action
    dispatch(receivedData(data));//这里的data是setTimeout()里的data
    }, 2500);
  }
};
const defaultState = {
  fetching: false,
  users: []
};
const asyncDataReducer = (state = defaultState, action) => {
  switch(action.type) {
    case REQUESTING_DATA:
      return {
        fetching: true,
        users: []
      }
    case RECEIVED_DATA:
      return {
        fetching: false,
        users: action.users
      }
    default:
      return state;
  }
};
const store = Redux.createStore(
  asyncDataReducer,
  Redux.applyMiddleware(ReduxThunk.default)
);//Redux.applyMiddleware()作为Redux.createStore()的第二个参数。

看个例子回顾一下:用Redux实现一个简单的计数器。

// Define a constant for increment action types
const INCREMENT = 'INCREMENT'; 
// Define a constant for decrement action types
const DECREMENT = 'DECREMENT'; 
// Define the counter reducer which will increment or decrement the state based on the action it receives
const counterReducer = (state=0,action)=>{
  switch(action.type){
    case INCREMENT:
    return state+1
    case DECREMENT:
    return state-1
    default:
    return state;
  }
}; 
// Define an action creator for incrementing
const incAction = ()=>{return{type:INCREMENT}}; 
// Define an action creator for decrementing
const decAction = ()=>{return{type:DECREMENT}}; 
// Define the Redux store here, passing in your reducers
const store = Redux.createStore(counterReducer); 
//发送一个action给store
store.dispatch({type:INCREMENT});

Redux并没有主动在其store或reducer中强制执行state不变性,这一责任落在程序员身上。好在,JavaScript(尤其是ES6)提供了一些有用的工具,无论它是字符串、数字、数组还是对象,使用这些工具都能增强其state的不可变性。注意,字符串和数字是原始值,本质上是不可变的。换句话说,3就是3,你不能改变数字3的值。然而,数组或对象是可变的。在实践中,我们的state可能由一个数组或对象组成,因为这在表示许多类型信息时是一种有用的数据结构。看个例子:

const ADD_TO_DO = 'ADD_TO_DO';
//表示要执行的任务的字符串列表:
const todos = [
  'Go to the store',
  'Clean the house',
  'Cook dinner',
  'Learn to code',
];
const immutableReducer = (state = todos, action) => {
  switch(action.type) {
    case ADD_TO_DO:
      {//不能改变state!而.push()和.splice()会改变原有数组,.concat()、.slice()和扩展操作符[…array]不改变原有数组只是返回一个新数组。
      return state.concat(action.todo);
      }
    default:
      return state;
  }
};
const addToDo = (todo) => {
  return {
    type: ADD_TO_DO,
    todo
  }
}
const store = Redux.createStore(immutableReducer);

ES6提供的一种帮助在Redux中实现状态不变性的解决方案是spread操作符:…,spread运算符有多种应用,其中一种就是从现有数组生成新数组,例如:

 let newArray = [...myArray]; 

newArray现在是myArray的克隆副本。这两个数组仍然各自单独存在于内存中。如果执行“newArray.push(5)”这样语句,改变的只是newArray,myArray不会改变。spread运算符'…'有效地将myArray中的值扩展到一个新数组中。要克隆数组并在新数组中添加其他值,可以写成:

[...myArray, 'new value']这将返回一个由myArray中的值、字符串new value作为最后一个值组成的新数组。扩展语法可以在这样的数组组合中多次使用,注意,它只会生成数组的浅拷贝,也就是说,它只为一维数组提供不可变的数组操作。看个例子:

const immutableReducer = (state = ['Do not mutate state!'], action) => {
  switch(action.type) {
    case 'ADD_TO_DO':
      //不要改变state
      return [...state,action.todo];
    default:
      return state;
  }
};
const addToDo = (todo) => {
  return {
    type: 'ADD_TO_DO',
    todo
  }
}
const store = Redux.createStore(immutableReducer);
//测试:
console.log(store.dispatch(addToDo('eat')));//{ type: 'ADD_TO_DO', todo: 'eat' }

假设state是一个数组,如何从state中删除指定项然后返回新state呢?

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
      //不要改变state!!
      //法一:
      //return [...state].slice(0,action.index).concat([...state].slice(action.index+1,state.length));//slice能复制已存在的数组元素,并以数组的形式返回这些元素。它接受2个参数,第一个参数是开始提取的索引,第二个参数是停止提取的索引(提取将持续到此,但不包括该索引处的元素)。如果未提供参数,则默认值是从数组的开始到结束,即会复制粘贴整个数组。只提供一个参数,则从该参数开始,一直复制到结尾。
      //法二:
      return [...state].filter((item,index)=>{return index!==action.index});//filter方法对数组的每个元素调用函数,最后返回一个新数组,该数组只包含该函数返回true的元素

    default:
      return state;
  }
};
const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}
const store = Redux.createStore(immutableReducer);

当state是一个对象时,有什么方法可以帮助增强state的不可变性呢?一个有用工具是Object.assign()。Object.assign()接受目标对象和来源对象作为参数(来源对象可以有多个),它会将属性从源对象映射到目标对象,任何匹配的属性都会被源对象中的属性覆盖。此行为通常用于通过传递一个空对象作为第一个参数,后跟要复制的对象来创建对象的浅层副本。下面是一个例子:

const newObject = Object.assign({}, obj1, obj2);    创建的newObject新对象包含了obj1和obj2中当前存在的属性。看个例子:

const defaultState = {
  user: 'CamperBot',
  status: 'offline',
  friends: '732,982',
  community: 'freeCodeCamp'
};
const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      //不要改变state!!
      return Object.assign({},state,{status:'online'});//对象是键值对的形式,当值是字符时不要忘了引号!
    default:
      return state;
  }
};
const wakeUp = () => {
  return {
    type: 'ONLINE'
  }
};
const store = Redux.createStore(immutableReducer);

React和Redux结合使用

React 是一个视图库(我们提供数据,然后它以一种高效、可预测的方式呈现视图)。Redux是一个状态管理框架,我们使用它来简化应用程序的state的管理。通常,在React Redux应用程序中,我们创建一个管理整个应用程序state的Redux store,React组件只订阅store中与其角色相关的数据,然后,我们直接从React组件发送action,这会触发store进行更新。

尽管React组件可以管理自己的本地state,但对于一个复杂的应用程序来说,通常最好使用Redux将应用程序state保存在一个位置(当然也有例外:单个组件可以具有仅针对它们的局部state)。最后,由于Redux不是被设计用于与React一起使用的,所以我们需要使用react-redux包,它提供了一种将Redux的state和dispatch作为props传递给React组件的方法。

下面,我们将创建一个简单的React组件,它允许我们输入新的文本消息,每点一次按钮,新文本会被添加到在视图中显示的messages数组末尾中。然后创建一个Redux store和action用来管理文本array的state。最后,使用react-redux将redux store与组件连接起来,从而将本地state提取到redux store中。

class DisplayMessages extends React.Component {
  constructor(props) {
    super(props);
    //记住state初始化是放在constructor方法里的!!
    this.state = {
      input: '',
      messages: []
    }
    this.handleChange=this.handleChange.bind(this);
    this.submitMessage=this.submitMessage.bind(this);
  }; 
  handleChange(e){
    //不能直接修改state的属性值,要通过setState修改state的属性值
    this.setState(
      {
      input:e.target.value,
      messages:this.state.messages//输入框一有更改,就把之前赋有值的messages数组再赋给当前messages数组
      }
    )
  };
  submitMessage(){  
    this.setState(
      {   
       messages:[...this.state.messages,this.state.input],//每点击一次按钮,就把当前输入框的内容添加到messages数组的最后一项
       input:''   
      }
    )
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input type='text' onChange={this.handleChange} value={this.state.input}/>{/*加value是为了让input元素呈现state里的input值*/}
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
        {this.state.messages.map((item)=>{
          return <li key={item}>{item}</li> })
        }
        </ul>
      </div>
    );
  }
};

完成了React组件之后,我们需要将它在本地执行的state逻辑提取到Redux中,这是将简单的React应用程序连接到Redux的第一步。上面的应用程序唯一的功能是将用户的新消息添加到无序列表ul中(为了演示React和Redux如何协同工作,这个示例很简单)。下面创建Redux:

const ADD='ADD';
//action创建器(为了添加message)
const addMessage=(message)=>{
  return {
    type:ADD,
    message//在返回的action中包含message
  }
}
//这个reducer应该将“message”添加到state中保存的messages数组中,或者返回当前的state。
const messageReducer=(state=[],action)=>{
  switch(action.type){
    case ADD:
    return [...state,action.message];
    default:
    return state;
  }
}
//Redux store(为了处理message的数组)
const store=Redux.createStore(messageReducer);

下一步是为React提供对Redux store的访问,以及它发送更新所需的action。React Redux提供了react-redux包来帮助我们完成这些任务。React Redux提供了一个具有两个关键特性的小型API:Provider和connect。先来看Provider ,它是React Redux的包装组件,用于包装我们的React应用程序,这个包装器允许我们访问整个组件树中的Redux store和dispatch函数。Provider需要两个props:Redux store和应用程序的子组件。为应用程序组件定义“Provider”语法如下:

<Provider store={store}>
  <App/>
</Provider>

使用Provider将上面的Redux连接到上面的React(都写在同一个JS文件里):

//AppWrapper中的render方法中要用到Provider
const Provider = ReactRedux.Provider;//React Redux在这里作为全局变量提供

class AppWrapper extends React.Component {
  //渲染Provider
  constructor(props){
    super(props);
  }
  render(){
    return (<Provider store={store}><DisplayMessages/></Provider>);
  }
};

完整代码如下:

// Redux:
const ADD = 'ADD';

const addMessage = (message) => {
  return {
    type: ADD,
    message
  }
};

const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};



const store = Redux.createStore(messageReducer);

// React:

class DisplayMessages extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {  
    this.setState((state) => {
      const currentMessage = state.input;
      return {
        input: '',
        messages: state.messages.concat(currentMessage)
      };
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange}/><br/>
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
          {this.state.messages.map( (message, idx) => {
              return (
                 <li key={idx}>{message}</li>
              )
            })
          }
        </ul>
      </div>
    );
  }
};

const Provider = ReactRedux.Provider;

class AppWrapper extends React.Component {
  constructor(props){
    super(props);
  }
  render(){
    return (<Provider store={store}><DisplayMessages/></Provider>);
  }
};
//不用再写下面这句了!!上面内容会通过API呈现到DOM。
//将AppWrapper呈现到DOM
//ReactDOM.render(<AppWrapper/>,document.getElementById('challenge-node'));
View Code

Provider组件允许我们向React组件提供state和dispatch,但必须准确指定我们想要的state或action,这样才能确保每个组件都只能访问其所需的state。我们可以通过创建两个函数来实现这一点:mapStateToProps() 和mapDispatchToProps()。在这些函数中,我们可以声明想要访问哪些state片段,以及能dispatch哪些action创建器。一旦这些函数就位,我们就能使用React Redux 的connect方法将它们连接到我们的组件。

看下mapStateToProps():

const state = [];
/*将数组作为参数state传递给mapStateToProps,返回一个对象,此对象的messages键的值为参数state代表的数组*/
/*法一(虽然可行,但不能这样定义,后面用时不方便。。。):
function mapStateToProps(state){
  return {messages:state}
};*/
//法二:
const mapStateToProps=(state)=>{
  return {messages:state}
}
/*在幕后,React Redux使用store.subscribe()方法实现mapStateToProps()*/

mapDispatchToProps()函数用于为React组件提供特定的action创建器,以便组件可以针对Redux store发送action。它的结构类似于mapStateToProps()函数,返回一个将发送的action映射到属性名称的对象,此属性名称将成为组件的props。不同的是,每个属性不是返回一段state,而是返回一个函数,该函数使用action创建器和任何相关的action数据调用dispatch。我们能访问这个dispatch,是因为在定义函数时它就作为参数传递给了mapDispatchToProps() ,就像我们将state传递给mapStateToProps()一样。在幕后,React Redux使用Redux的store.dispatch()方法实现mapDispatchToProps()里的这些dispatch,这与它对映射到state的组件使用store.subscribe()的方式类似。例如,有一个叫 loginUser()的action创建器,它将username作为action的有效载荷,从mapDispatchToProps()为这个action创建器返回的对象如下所示:

{
  submitLoginUser: function(username) {
    dispatch(loginUser(username));
  }
}

看下mapDispatchToProps():

//action创建器
const addMessage = (message) => {
  return {
    type: 'ADD',
    message: message
  }
};
//编写mapDispatchToProps()函数:
const mapDispatchToProps=(dispatch)=>{
  return {
    submitNewMessage:function(newMessage){dispatch(addMessage(newMessage))}
  };//返回一个对象,此对象有一个submitNewMessage属性,值为dispatch函数,该函数在发送addMessage()时为新message添加一个参数newMessage。
};

现在已经编写了mapStateToProps()和mapDispatchToProps()函数,我们可以使用它们将state和dispatch映射到某一个React组件的props,那么具体怎么操作呢?React Redux中的connect方法可以帮我们完成此步骤,此方法接受两个可选参数:mapStateToProps() 和mapDispatchToProps()。为什么是可选的?因为我们可能有一个只需要访问state但不需要发送任何action的组件,反之亦然。使用此方法时,先将函数作为参数传入,然后立即使用组件调用结果。这个语法有点不寻常,就长下面这样:

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

 注意:如果想省略connect方法的一个参数,在其对应位置传个null即可。看个具体例子:

const addMessage = (message) => {
  return {
    type: 'ADD',
    message: message
  }
};
//mapStateToProps函数
const mapStateToProps = (state) => {
  return {
    messages: state
  }
};
//mapDispatchToProps函数
const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message));
    }
  }
};
//Presentational类组件
class Presentational extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <h3>This is a Presentational Component</h3>
  }
};
//connect方法
const connect = ReactRedux.connect;
//使用ReactRedux全局对象中的Connect方法将组件Presentational连接到Redux:
const ConnectedComponent=connect(mapStateToProps,mapDispatchToProps)(Presentational);
/*最后DOM页面呈现如下:
This is a Presentational Component
*/

既然我们知道了如何使用connect将React连接到Redux,就能将所学应用到例子中处理messages的React组件中。上面的例子中,连接到Redux的组件被命名为Presentational,这可不是随意的,该术语通常指不直接连接到Redux的React组件,他们只是负责UI的呈现(根据收到的props来呈现)。相比之下,连接到Redux的容器组件,通常负责将action发送到store,并通常将store state作为props传递给子组件。看代码:

// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
  return {
    type: ADD,
    message: message
  }
};
const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};
const store = Redux.createStore(messageReducer);
// React:
class Presentational extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      messages: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {
    this.setState((state) => {
      const currentMessage = state.input;
      return {
        input: '',
        messages: state.messages.concat(currentMessage)
      };
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange}/><br/>
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
          {this.state.messages.map( (message, idx) => {
              return (
                 <li key={idx}>{message}</li>
              )
            })
          }
        </ul>
      </div>
    );
  }
};
// React-Redux:
const mapStateToProps = (state) => {
  return { messages: state }
};
const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (newMessage) => {
       dispatch(addMessage(newMessage))
    }
  }
};
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
//定义容器组件Container,使用connect方法将Presentational组件连接到Redux:
const Container=connect(mapStateToProps,mapDispatchToProps)(Presentational);

class AppWrapper extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {   
    return (    
      <Provider store={store}>{/*呈现React Redux的Provider组件。将Redux的store作为prop传给Provider。*/}
        <Container/>{/*将Container作为子组件呈现*/}
      </Provider>
    );
  }
};

我们已经编写了所有的Redux代码,以便Redux可以控制我们的React消息应用程序的state管理。现在Redux已连接,但state还是在Presentational组件的本地处理,我们需要将state管理从Presentational组件提取到Redux中。name如何将本地state提取到Redux?看例子:

// Redux:
const ADD = 'ADD';
const addMessage = (message) => {
  return {
    type: ADD,
    message: message
  }
};
const messageReducer = (state = [], action) => {
  switch (action.type) {
    case ADD:
      return [
        ...state,
        action.message
      ];
    default:
      return state;
  }
};
const store = Redux.createStore(messageReducer);
// React:
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
//删除本地state的messages属性,message将由Redux管理
class Presentational extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: ''
      //messages: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  //修改submitMessage函数,使其从this.props发送submitNewMessage(),从state传入目前输入的message作为参数(因为从本地state中删除了messages,所以在这里也从对this.setState()的调用中删除messages属性)。
  submitMessage() {
    this.props.submitNewMessage(this.state.input);
    this.setState({   
      input: ''
      //messages: state.messages.concat(state.input)   
    });
  }
  render() {
    return (
      <div>
        <h2>Type in a new Message:</h2>
        <input
          value={this.state.input}
          onChange={this.handleChange}/><br/>
        <button onClick={this.submitMessage}>Submit</button>
        <ul>
        {/*修改以下呈现的数据,以使其映射呈现从props(而不是从state)接收到的消息
         {this.state.messages.map((item, index) => {
              return (
                 <li key={index}>{item}</li>
              )
          })
         } 
        */}
         {this.props.messages.map((item, index) => {
              return (
                 <li key={index}>{item}</li>
              )
          })
         }
        </ul>
      </div>
    );
  }
};
//映射对象给props(将messages属性传给props)
const mapStateToProps = (state) => {
  return {messages: state}
};
//映射发送action的方法给props(将submitNewMessage方法传给props)
const mapDispatchToProps = (dispatch) => {
  return {
    //注意,submitNewMessage是方法!接收一个message参数。
    submitNewMessage: (message) => {
      dispatch(addMessage(message))
    }
  }
};
//使用connect将React连接到Redux
//定义子组件Container,Container是连接到Redux的容器组件,通常负责将action发送到store,并通常将store的state作为props传递给子组件
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
//Provider是React Redux的包装组件,用于包装我们的React应用程序,这个包装器允许我们访问整个组件树中的Redux store和dispatch函数。Provider需要两个props:Redux store和应用程序的子组件
class AppWrapper extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <Container/>
      </Provider>
    );
  }
};

以上为Redux管理state的例子【此示例还说明了与此同时组件还能具有本地状态:组件仍然在其本地state下跟踪用户的input】。

Redux就是这样在React之上提供有用的state管理框架的。最初只使用React的本地state就获得了相同的结果,这通常在简单的应用程序中可行。然而,随着我们的应用程序越来越大、越来越复杂,我们的state管理也越来越复杂。这就是Redux解决的问题。 

在我们自己的机器上使用npm和文件系统,语法是什么样子的呢?除了import语句(import语句引入了保证代码能正常运行的所有依赖项),其他代码看起来和上面例子类似。最后,编写React和Redux代码通常需要一些配置,这会变得有些复杂,参考文章https://www.freecodecamp.org/news/install-react-with-create-react-app/ ,文中讲解了如何使用create-react-app安装React.js。

在我们的机器上编写React应用程序的大概语法如下:

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider, connect } from 'react-redux'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

import rootReducer from './redux/reducers'
import App from './components/App'

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

。。。

posted @ 2022-10-25 21:32  枭二熊  阅读(86)  评论(0编辑  收藏  举报