# 安装 Redux

npm install redux

# 使状态完全从组件中分离开

  1. 动作对应用状态的影响是用一个 reducer 来定义的
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    case 'ZERO':
      return 0
    default: // if none of the above matches, code comes here
      return state
  }
}

第一个参数是当前状态,第二个参数是动作对象。

  1. Reducer 不应该从应用代码中直接调用,只是作为创建存储的 createStore 函数的参数
import { createStore } from 'redux'
const counterReducer = (state = 0, action) => {
  // ...
}
const store = createStore(counterReducer)
  1. Reduxstore 对象提供了 getState 方法,用于获取当前状态,以及 dispatch 方法,用于触发动作
const store = createStore(counterReducer)
console.log(store.getState()) // 0
store.dispatch({type: 'INCREMENT'})
store.dispatch({type: 'INCREMENT'})
store.dispatch({type: 'INCREMENT'})
console.log(store.getState()) // 3
store.dispatch({type: 'ZERO'})
store.dispatch({type: 'DECREMENT'})
console.log(store.getState()) // -1

# 状态管理

  1. 项目目录:
redux-anecdotes/
├── src/
│   ├── components/          # 可复用组件
│   │   └── AnecdoteList.jsx # 展示段子列表组件
│   │   └── AnecdoteForm.jsx # 创建新段子的表单组件
│   ├── store/
│   │   ├── actions/        # Redux action creators
│   │   │   └── anecdoteActions.js
│   │   ├── reducers/       # Redux reducers
│   │   │   └── anecdoteReducer.js
│   │   └── store.js        # Redux store 配置
│   ├── services/           # API 服务层(如果需要后端连接)
│   ├── App.jsx             # 主组件(你现有的文件)
│   └── main.jsx            # 应用入口文件
├── public/
│   └── index.html          # 主页面模板
├── package.json
└── README.md
  1. app.jsx 中只有封装了
import AnecdoteForm from './components/AnecdoteForm'
import AnecdoteList from './components/AnecdoteList'
const App = () => {
  return (
    <div>
      <AnecdoteList />
      <AnecdoteForm />
    </div>
  )
}
export default App
  1. 所有的状态管理放在 Reducer 中,包括了点赞和添加的对象组成
const anecdotesAtStart = [
  'If it hurts, do it more often',
  'Adding manpower to a late software project makes it later!',
  'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
  'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
  'Premature optimization is the root of all evil.',
  'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.'
]
const getId = () => (100000 * Math.random()).toFixed(0)
const asObject = (anecdote) => {
  return {
    content: anecdote,
    id: getId(),
    votes: 0
  }
}
const initialState = anecdotesAtStart.map(asObject)
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'VOTE': {
      const id = action.data.id
      const anecdoteToChange = state.find(a => a.id === id)
      const changeanedote = {
        ...anecdoteToChange,
        votes: anecdoteToChange.votes + 1
      }
      return state.map(a => a.id !== id ? a : changeanedote)
    }
    case 'ADD': {
      const newAnecdote = asObject(action.data.content)
      return state.concat(newAnecdote)
    }
  }
  return state
}
export const Vote = (id) => {
  return {
    type: 'VOTE',
    data: {
      id
    }
  }
}
export const Add = (content) => {
  return {
    type: 'ADD',
    data: {
      content
    }
  }
}
export default reducer
  1. AnecdoteList.jsx 中,处理了显示数据和点赞的逻辑
    通过调用 useDispatchuseSelector 来获取 store 中的状态和 dispatch 方法
import { useDispatch, useSelector } from "react-redux"
import { Vote } from "../reducers/anecdoteReducer"
const AnecdoteList = () => {
    const anecdotes = useSelector(state => state)
    const dispatch = useDispatch()
    const vote = (id) => {
        dispatch(Vote(id))
    }
    anecdotes.sort((a, b) => a.votes - b.votes)
    return (
        <div>
            <h2>Anecdotes</h2>
            {anecdotes.map(anecdote =>
                <div key={anecdote.id}>
                <div>
                    {anecdote.content}
                </div>
                <div>
                    has {anecdote.votes}
                    <button onClick={() => vote(anecdote.id)}>vote</button>
                </div>
                </div>
            )}
        </div>
    )
}
export default AnecdoteList
  1. AnecdoteForm.jsx 中,处理了添加新 anecdote 的逻辑
    通过调用 useDispatch 来获取 store 中的 dispatch 方法
import { useDispatch } from 'react-redux'
import { Add } from '../reducers/anecdoteReducer'
const AnecdoteForm = () => {
    const dispatch = useDispatch()
    const addanect = (e) => {
        e.preventDefault()
        const content = e.target.anecdote.value
        e.target.anecdote.value = ''
        dispatch(Add(content))
      }
    return (
        <div>
            <h2>create new</h2>
            <form onSubmit={addanect}>
                <div><input name='anecdote' /></div>
                <button type='submit'>create</button>
            </form>
        </div>
    )
}
export default AnecdoteForm
  1. main.jsx 中,将 App 组件渲染到 #root 元素中,并订阅 store 的变化,当 store 发生变化时,重新渲染 App 组件,主要通过 Provider 组件来提供 storeApp 组件
import ReactDOM from 'react-dom/client'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './App'
import reducer from './reducers/anecdoteReducer'
const store = createStore(reducer)
ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
)

# 再来点更多的 reducers

对于我们的 state 可以有多个子状态,比如 anecdotesfilter ,所以我们需要创建多个 reducer 来处理 anecdotesfilter 的状态。
对于各自的 reducer 就写自己的逻辑,比如 anecdotesreducer 可以这样写:

const anecdotesAtStart = [
  'If it hurts, do it more often',
  'Adding manpower to a late software project makes it later!',
  'The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.',
  'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
  'Premature optimization is the root of all evil.',
  'Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.'
]
const getId = () => (100000 * Math.random()).toFixed(0)
const asObject = (anecdote) => {
  return {
    content: anecdote,
    id: getId(),
    votes: 0
  }
}
const initialState = anecdotesAtStart.map(asObject)
const anecdoteReducer = (state = initialState, action) => {
  // console.log('state now: ', state)
  switch (action.type) {
    case 'VOTE': {
      const id = action.data.id
      const anecdoteToChange = state.find(a => a.id === id)
      const changeanedote = {
        ...anecdoteToChange,
        votes: anecdoteToChange.votes + 1
      }
      return state.map(a => a.id !== id ? a : changeanedote)
    }
    case 'ADD': {
      const newAnecdote = asObject(action.data.content)
      return state.concat(newAnecdote)
    }
    default: return state
  }
}
export const Vote = (id) => {
  return {
    type: 'VOTE',
    data: {
      id
    }
  }
}
export const Add = (content) => {
  return {
    type: 'ADD',
    data: {
      content
    }
  }
}
export default anecdoteReducer

而对于 noticereducer 可以这样写:

const notificationAtStart = "you did something"
const notificationReducer = (state = notificationAtStart, action) => {
  switch (action.type) {
    case 'NOTIFICATION':
      return action.data
    default:
      return state
  }
}
export const createNotification = (content) => {
    return {
        type: 'NOTIFICATION',
        data: content
    }
}
export default notificationReducer

一个是通知一个是显示,两种逻辑
main.js 中我们需要引入两个 reducer ,并创建 store ,然后把 store 传递给 App 组件,这样 App 组件就可以通过 useSelectoruseDispatch 来获取 store 中的状态和 dispatch 方法。
这里我们使用 Redux 工具包的 configureStore 函数创建商店

const store = configureStore({
  reducer: {
    anecdotes: anecdoteReducer,
    notification: notificationReducer
  }
})

state 就是一个包含两个子状态的对象, anecdotesnotification ,分别对应 anecdoteReducernotificationReducer
在访问的时候就要注意了