Skip to content

redux #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Twlig opened this issue Jul 15, 2022 · 0 comments
Open

redux #105

Twlig opened this issue Jul 15, 2022 · 0 comments
Labels

Comments

@Twlig
Copy link
Owner

Twlig commented Jul 15, 2022

redux

redux和mobx一样也是集中式状态管理。有3个关键概念。

  • Action:对象,里面记录了操作type和待修改数据参数。
  • Reducer,纯函数(输入相同,输出相同),接收两个参数,一是旧状态,二是action对象。然后在reducer中编写每个action.type对应需要执行的操作。注意,reducer要是纯函数,因此,不能在reduer中修改旧状态。而是需要通过返回一个新状态,交给dispatch根据新的返回值去修改state。
  • Store,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

基础应用

  1. 入口文件(index.jsx):

    项目的入口文件

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
  1. 创建Action(actions/index.js):

    生成action对象的工厂函数文件。其实不写这个直接在dispatch的时候store.dispatch({type: 'ADD', id: xxx,text:xxx})也可以。

let nextTodoId = 0
export const addTodo = text => {
  return {
    type: 'ADD_TODO',
    id: nextTodoId++,
    text
  }
}

export const setVisibilityFilter = filter => {
  return {
    type: 'SET_VISIBILITY_FILTER',
    filter
  }
}

export const toggleTodo = id => {
  return {
    type: 'TOGGLE_TODO',
    id
  }
}
  1. Reducers:

  • reducers/todos.js
const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        (todo.id === action.id) 
          ? {...todo, completed: !todo.completed}
          : todo
      )
    default:
      return state
  }
}

export default todos
  • reducers/visibilityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default visibilityFilter
  • reducers/index.js

    combineReducers合并所有reducer

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

const todoApp = combineReducers({
  todos,
  visibilityFilter
})
export default todoApp
  1. UI组件

    redux中有有一个重要的概念就是容器组件和UI组件。容器组件负责和操作state,dispatch action。而UI组件被容器组件包裹,容器组件以props的方式给UI组件传递 state和dispatch操作函数。这种分离的做法会让UI组件更加纯粹,能够实现低耦合,高复用。

  • components/TodoList.js
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo => (
      <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
    ))}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      completed: PropTypes.bool.isRequired,
      text: PropTypes.string.isRequired
    }).isRequired
  ).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

export default TodoList
  1. 容器组件

react-rudex是一个用来连接UI组件和容器组件的工具,使用connect方法。

connect(mapstatetoprops, mapdispatchtoprops)(component),返回容器组件。这个方法会把定义的UI组件所需的state属性和定义好的dispatch方法挂载到UI组件的props属性上。UI组件直接通过props属性调用相应的数据和方法。

  • containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    case 'SHOW_ALL':
    default:
      return todos
  }
}

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList
  1. 异步Action

前面介绍的都是同步示例,redux还可以处理异步数据流。前面介绍过reducer是纯函数,接收相同的输入产生相同的输出,那异步操作肯定不能在reducer中执行。redux设计的方案是在action中执行异步操作,也就是定义异步action,在异步action中还能调用同步action。因此异步action不需要生成action对象,而是返回函数,直接在action中定义异步操作,不需要再经过reducer

存在的问题:

  • redux的createStore默认只支持dispatch接收一个action对象然后交给reducer执行,而不是action函数。
  • action异步函数,可能需要调用同步action,因此需要dispatch方法,但是这个方法只在store中。

解决方法:

  • createStore可以配置第二个参数,applyMiddleware表示启用中间件,让dispatch可以接收action函数

  • redux-thunk中的thunkMiddleware,会在action返回的函数中自动带上dispatch参数。

  • actions.js

import fetch from 'cross-fetch'

export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
  return {
    type: REQUEST_POSTS,
    subreddit
  }
}

export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
  return {
    type: RECEIVE_POSTS,
    subreddit,
    posts: json.data.children.map(child => child.data),
    receivedAt: Date.now()
  }
}

 export const INVALIDATE_SUBREDDIT = ‘INVALIDATE_SUBREDDIT’
 export function invalidateSubreddit(subreddit) {
   return {
     type: INVALIDATE_SUBREDDIT,
     subreddit
   }
 }

// 来看一下我们写的第一个 thunk action 创建函数!
// 虽然内部操作不同,你可以像其它 action 创建函数 一样使用它:
// store.dispatch(fetchPosts('reactjs'))

export function fetchPosts(subreddit) {

  // Thunk middleware 知道如何处理函数。
  // 这里把 dispatch 方法通过参数的形式传给函数,
  // 以此来让它自己也能 dispatch action。

  return function (dispatch) {

    // 首次 dispatch:更新应用的 state 来通知
    // API 请求发起了。

    dispatch(requestPosts(subreddit))

    // thunk middleware 调用的函数可以有返回值,
    // 它会被当作 dispatch 方法的返回值传递。

    // 这个案例中,我们返回一个等待处理的 promise。
    // 这并不是 redux middleware 所必须的,但这对于我们而言很方便。

    return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        // 不要使用 catch,因为会捕获
        // 在 dispatch 和渲染中出现的任何错误,
        // 导致 'Unexpected batch number' 错误。
        // https://github.com/facebook/react/issues/6895
         error => console.log('An error occurred.', error)
      )
      .then(json =>
        // 可以多次 dispatch!
        // 这里,使用 API 请求结果来更新应用的 state。

        dispatch(receivePosts(subreddit, json))
      )
  }
}
  • index.js
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import { selectSubreddit, fetchPosts } from './actions'
import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const store = createStore(
  rootReducer,
  applyMiddleware(
    thunkMiddleware, // 允许我们 dispatch() 函数
    loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志
  )
)

store.dispatch(selectSubreddit('reactjs'))
store
  .dispatch(fetchPosts('reactjs'))
  .then(() => console.log(store.getState())
)
@Twlig Twlig added the react label Jul 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant