freeCodeCamp刷题笔记之Redux

Redux 期望所有状态更新都是使用不可变的方式

先了解术语

Action

action是一个具有 type 字段的普通 JavaScript 对象。你可以将 action 视为描述应用程序中发生了什么的事件。type 字段是一个字符串,给这个 action 一个描述性的名字,比如"todos/todoAdded"。我们通常把那个类型的字符串写成“域/事件名称”,其中第一部分是这个 action 所属的特征或类别,第二部分是发生的具体事情。action 对象可以有其他字段,其中包含有关发生的事情的附加信息。按照惯例,我们将该信息放在名为 payload 的字段中。一个典型的 action 对象可能如下所示:

const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}

Action Creator

action creator 是一个创建并返回一个 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象:

const addTodo = text => {
  return {
    type: 'todos/todoAdded',
    payload: text
  }
}

Reducer

Reducer 是一个函数,也可以视为事件监听器,接收当前的 state 和一个 action 对象,根据接收到的 action(事件)类型处理事件,必要时决定如何更新状态,并返回新状态。

Reducer 必需符合以下规则:

  • 仅使用 state 和 action 参数计算新的状态值
  • 禁止直接修改 state。必须通过复制现有的 state 并对复制的值进行更改的方式来做 不可变更新(immutable updates)
  • 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // 检查 reducer 是否关心这个 action
  if (action.type === 'counter/increment') {
    // 如果是,复制 `state`
    return {
      ...state,
      // 使用新值更新 state 副本
      value: state.value + 1
    }
  }
  // 返回原来的 state 不变
  return state
}

创建一个 Redux Store

在 Redux 中,有一个状态对象负责应用程序的整个状态, 这意味着如果你有一个包含十个组件且每个组件都有自己的本地状态的 React 项目,那么这个项目的整个状态将通过 Redux store 被定义为单个状态对象, 这是学习 Redux 时要理解的第一个重要原则:Redux store 是应用程序状态的唯一真实来源。这也意味着,如果应用程序想要更新状态,只能通过 Redux store 执行, 单向数据流可以更轻松地对应用程序中的状态进行监测管理。

const reducer = (state = 5) => {
  return state;
}
// 可从 Redux 对象获得 Redux 方法
// 例如:Redux.createStore()
// 在这里定义 store here:
const store = Redux.createStore(reducer,5)

从 Redux Store 获取状态

const store = Redux.createStore(
  (state = 5) => state
);
const currentState=store.getState()

定义一个 Redux Action

由于 Redux 是一个状态管理框架,因此更新状态是其核心任务之一。 在 Redux 中,所有状态更新都由 dispatch action 触发, action 只是一个 JavaScript 对象,其中包含有关已发生的 action 事件的信息。 Redux store 接收这些 action 对象,然后更新相应的状态。 有时,Redux action 也会携带一些数据。 例如,在用户登录后携带用户名, 虽然数据是可选的,但 action 必须带有 type 属性,该属性表示此 action 的类型。我们可以将 Redux action 视为信使,将有关应用程序中发生的事件信息提供给 Redux store, 然后 store 根据发生的 action 进行状态的更新。

const action=({
    type:'LOGIN'
})

定义一个 Action Creator通过

const action = {
  type: 'LOGIN'
}
const actionCreator=()=>{
  return action;
}

分发Action Event

const store = Redux.createStore(
  (state = {login: false}) => state
);
const loginAction = () => {
  return {
    type: 'LOGIN'
  }
};
store.dispatch(loginAction())  // 等效于store.dispatch({ type: 'LOGIN' });

在 Store 里处理 Action

在一个 action 被创建并 dispatch 之后,Redux store 需要知道如何响应该操作。 这就是 reducer 函数存在的意义。 Redux 中的 Reducers 负责响应 action 然后进行状态的修改。 reducer 将 state 和 action 作为参数,并且它总是返回一个新的 state。reducer 的唯一的作用:只是一个接受状态和动作,然后返回新状态的纯函数。Redux 的另一个关键原则是 state 是只读的。 换句话说,reducer 函数必须始终返回一个新的 state,并且永远不会直接修改状态。 

const defaultState = {
  login: false
};

const reducer = (state = defaultState, action) => {
  if(action.type === 'LOGIN'){
    return {
      login:true
    }
  }
  return state;
};

const store = Redux.createStore(reducer);

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

用switch处理多个action

const defaultState = {
  authenticated: false
};
const authReducer = (state = defaultState, action) => {
  switch(action.type){
    case 'LOGIN':
      return {
        authenticated : true
      }
    case 'LOGOUT':
      return {
        authenticated : false
      }
    default:
      return defaultState;
  }
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
  return {
    type: 'LOGIN'
  }
};
const logoutUser = () => {
  return {
    type: 'LOGOUT'
  }
};

注册store监听器

在 Redux store 对象*问数据的另一种方法是 store.subscribe()。 这允许将监听器函数订阅到 store,只要 action 被 dispatch 就会调用它们。 这个方法的一个简单用途是为 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);

let count = 0;
store.subscribe(() =>{
  ++count
})

store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);
store.dispatch({type: ADD});
console.log(count);

组合多个 Reducers

const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 定义多个 reducer 来处理应用程序状态的不同部分
const counterReducer = (state = 0, action) => {
    switch (action.type) {
        case INCREMENT:
            return ++state;
        case DECREMENT:
            return --state;
        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;
    }
};
// 将这些 reducer 组合成一个 root reducer
const rootReducer = Redux.combineReducers({
    count: counterReducer,
    auth: authReducer
});
// 然后将 root reducer 传递给 Redux createStore()方法。
const store = Redux.createStore(rootReducer);

发送 Action Data 给 Store

const ADD_NOTE = 'ADD_NOTE';

const notesReducer = (state = 'Initial State', action) => {
  switch(action.type) {
    case ADD_NOTE:
      return action.text;
    default:
      return state;
  }
};
// addNoteText 函数就是一个 Action Creator
const addNoteText = (note) => {
    return {
      type:ADD_NOTE,
      text:note
    }
};

const store = Redux.createStore(notesReducer);

console.log(store.getState()); // 这是初始data
store.dispatch(addNoteText('Hello!')); 
// store.dispatch()是 View 发出 Action 的唯一方法
console.log(store.getState()); // 这是dispatch后的action data

使用中间件处理异步操作

在某些时候,需要在 Redux 应用程序中使用异步请求,那么如何处理这些类型的请求? Redux 中间件专为此目的而设计,称为 Redux Thunk 中间件。 这里简要介绍如何在 Redux 中使用它。如果要使用 Redux Thunk 中间件,请将其作为参数传递给 Redux.applyMiddleware()。 然后将此函数作为第二个可选参数提供给 createStore() 函数, 看一下编辑器底部的代码。 然后,要创建一个异步的 action,需要在 action creator 中返回一个以 dispatch 为参数的函数。 在此示例中,使用 setTimeout() 模拟异步请求。 通常在执行异步行为之前 dispatch action,以便应用程序状态知道正在请求某些数据(例如,这个状态可以显示加载图标)。 然后,一旦收到数据,就会发送另一个 action,该 action 的 data 是请求返回的数据同时也代表 API 操作完成。(看不懂)

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) {
        dispatch(requestingData());
        setTimeout(function () {
            let data = {
                users: ['Jeff', 'William', 'Alice']
            }
            dispatch(receivedData(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写一个计数器

// 为 decrement action types 定义一个常量
const INCREMENT = 'INCREMENT'; 
const DECREMENT = 'DECREMENT';  

// 定义 counter reducer,根据接收到的动作递增或递减 state
const counterReducer = (state=0,action)=>{
  switch(action.type){
    case INCREMENT:
        return state + 1;
    case DECREMENT:
        return state - 1;
    default:
        return state;
  }
}; 

const incAction = ()=>{
  return {
    type:'INCREMENT'
  }
}; // 为自增运算定义一个动作创建器

const decAction = () =>{
  return {
    type:'DECREMENT'
  }
}; // 为自减运算定义一个动作创建器
// 在这里定义 Redux store,传入 reducers
const store = Redux.createStore(counterReducer); 

使用扩展运算符强制执行状态不变性

要注意扩展运算符它只是生成数组的浅拷贝副本。 也就是说,它只为一维数组提供不可变的数组操作。

从数组中删除项目

const immutableReducer = (state = [0,1,2,3,4,5], action) => {
  switch(action.type) {
    case 'REMOVE_ITEM':
      // 使用 slice() 从已有的数组显示选取的元素
      return [...state.slice(0,action.index),
      ...state.slice(action.index+1,state.length)]
    default:
      return state;
  }
};

const removeItem = (index) => {
  return {
    type: 'REMOVE_ITEM',
    index
  }
}

const store = Redux.createStore(immutableReducer);

使用 Object.assign 拷贝对象

const immutableReducer = (state = defaultState, action) => {
  switch(action.type) {
    case 'ONLINE':
      return Object.assign({},state,{status:'online'})
    default:
      return state;
  }
};

提取状态逻辑给Redux

const ADD = 'ADD';
const addMessage = (message) => {
    return {
        type: 'ADD',
        message
    }
}
const messageReducer = (state=[],action) => {
    if(action.type =='ADD'){
        return [...state,action.message];
    }else{
        return state;
    }

}
const store = Redux.createStore(messageReducer)

使用 Provider 连接 Redux 和 React

把 Redux 的 store 作为 props 传入。

// 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;
// 使用 Provider 连接 Redux 和 React
class AppWrapper extends React.Component {
  render(){
    return (
      <Provider store={store} >
        <DisplayMessages />
      </Provider>
    )
  }
};

映射 Dispatch 到 Props

这里我也看不懂为什么这么写。。。

const addMessage = (message) => {
    return {
        type: 'ADD',
        message: message
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
      submitNewMessage:(message)=>{
        dispatch(addMessage(message))
      }
    }
}

连接 Redux 和 React

如果要省略 connect 方法中的某个参数,则应当用 null 替换这个参数。

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

const mapStateToProps = (state) => {
  return {
    messages: state
  }
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message));
    }
  }
};

class Presentational extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <h3>This is a Presentational Component</h3>
  }
};

const connect = ReactRedux.connect;
const ConnectedComponent  = connect(mapStateToProps, mapDispatchToProps)(Presentational);

将局部状态提取到 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;

class Presentational extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
    }
    this.handleChange = this.handleChange.bind(this);
    this.submitMessage = this.submitMessage.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  submitMessage() {
    this.props.submitNewMessage(this.state.input);
    this.setState((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>
          {this.props.messages.map( (message, idx) => {
              return (
                 <li key={idx}>{message}</li>
              )
            })
          }
        </ul>
      </div>
    );
  }
};

const mapStateToProps = (state) => {
  return {messages: state}
};

const mapDispatchToProps = (dispatch) => {
  return {
    submitNewMessage: (message) => {
      dispatch(addMessage(message))
    }
  }
};

const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);

class AppWrapper extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <Container/>
      </Provider>
    );
  }
};

上一篇:FreeCodeCamp javascript基础最后一节 递归调用练习


下一篇:freeCodeCamp刷题笔记之React