# redux-demos **Repository Path**: qinpmc/redux-shopping-car ## Basic Information - **Project Name**: redux-demos - **Description**: redux-demos - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-04-12 - **Last Updated**: 2023-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Getting Started with Create React App This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). ## Available Scripts In the project directory, you can run: ### `npm start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in your browser. The page will reload when you make changes.\ You may also see any lint errors in the console. ### `npm test` Launches the test runner in the interactive watch mode.\ See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. ### `npm run build` Builds the app for production to the `build` folder.\ It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. ### `npm run eject` **Note: this is a one-way operation. Once you `eject`, you can't go back!** If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. ## Learn More You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). To learn React, check out the [React documentation](https://reactjs.org/). ### Code Splitting This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) ### Analyzing the Bundle Size This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) ### Making a Progressive Web App This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) ### Advanced Configuration This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) ### Deployment This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) ### `npm run build` fails to minify This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) # 远程仓库重命名 git remote remove origin //移除原来仓库地址 git remote add origin https://gitee.com/qinpmc/redux-shopping-car.git //添加新仓库地址 git push -u origin master //第一次设置默认的远程分支 # 资源地址 - https://www.redux.org.cn/ - https://github.com/happypoulp/redux-tutorial - https://segmentfault.com/a/1190000011474522?utm_source=tag-newest (shopping-cart 分支) - https://juejin.cn/post/6844904021187117069 # 核心概念 1. state state 对象就像 “Model”,区别是它并没有 setter(修改器方法)。因此其它的代码**不能随意**修改它,造成难以复现的 bug。 todo 应用的 state 可能长这样: ``` { todos: [{ text: 'Eat food', completed: true }, { text: 'Exercise', completed: false }], visibilityFilter: 'SHOW_COMPLETED' } ``` 2. action - 要想更新 state 中的数据,你需要发起一个 action。**Action 就是一个普通 JavaScript 对象** - 强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。 - action 就像是描述发生了什么的指示器。 一些 action 的示例: ``` { type: 'ADD_TODO', text: 'Go to swimming pool' } { type: 'TOGGLE_TODO', index: 1 } { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' } ``` 3. reducer - **把 action 和 state 串起来**,开发一些**函数,这就是 reducer** - reducer 只是一个接收 state 和 action,并 **返回新的 state**的函数。 - 对于大的应用来说,不大可能仅仅只写一个这样的函数,所以编写很多**小函数**来分别管理 state 的一部分. - **参数为 state, action** ``` function visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter; } else { return state; } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([{ text: action.text, completed: false }]); case 'TOGGLE_TODO': return state.map((todo, index) => action.index === index ? { text: todo.text, completed: !todo.completed } : todo ) default: return state; } } ``` 再开发一个 reducer 调用这两个 reducer,进而来管理整个应用的 state: ``` function todoApp(state = {}, action) { return { todos: todos(state.todos, action), visibilityFilter: visibilityFilter(state.visibilityFilter, action) }; } ``` # 三大原则 ## 1 单一数据源 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于**唯一一个 store** 中。 ``` console.log(store.getState()) /* 输出 { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] } */ ``` ## 2 State 是只读的 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。 ``` store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' }) ``` ## 3 Reducer 使用纯函数来执行修改 为了描述 action 如何改变 state tree ,你需要编写 reducers。 Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。 刚开始你可以只有一个 reducer,**随着应用变大,你可以把它拆成多个小的 reducers**,分别独立地操作 state tree 的不同部分 ``` function visibilityFilter(state = 'SHOW_ALL', action) { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ] case 'COMPLETE_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer) ``` # 基础 ## Action Action 是把数据从应用传到 store 的有效载荷。 它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。 ``` const ADD_TODO = 'ADD_TODO' { type: ADD_TODO, text: 'Build my first Redux app' } ``` ### Action 创建函数 ``` function addTodo(text) { return { type: ADD_TODO, text } } ``` ## Reducer 注意点: 1. 不要修改 state 错误: 不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值 正确:Object.assign({}, state, { visibilityFilter: action.filter }) 正确:{ ...state, ...newState } 2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。 3. reducer 一定要保持纯净。 - 只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。 4. 拆分 Reducer ``` function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] case TOGGLE_TODO: return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: !todo.completed }) } return todo }) default: return state } } function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } } export default function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), // 注意:这个reducer 操作state.visibilityFilter todos: todos(state.todos, action) // 注意:这个reducer 操作state.todos } } ////////////////////////////////// /* import { combineReducers } from 'redux' 这里和上面的 导出完全等价 const todoApp = combineReducers({ visibilityFilter, todos }) export default todoApp */ ``` 5. 注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。 ## Store Store 就是把它们联系到一起的对象。Store 有以下职责: - 维持应用的 state; - 提供 getState() 方法获取 state; - 提供 dispatch(action) 方法更新 state; - 通过 subscribe(listener) 注册监听器; - 通过 subscribe(listener) 返回的函数注销监听器。 - createStore(reducer, [preloadedState 初始state], enhancer) ``` var reducer = function (...args) { console.log('Reducer was called with args', args) } var store_1 = createStore(reducer) //Output: Reducer was called with args [ undefined, { type: '@@redux/INIT' } ] // reducer 在createStore 时已经执行,并没有执行dispatch ``` ``` import { createStore } from 'redux' import todoApp from './reducers' let store = createStore(todoApp) // createStore() 的第二个参数是可选的, 用于设置 state 初始状态 let store = createStore(todoApp, window.STATE_FROM_SERVER) ``` ``` import { addTodo, toggleTodo, setVisibilityFilter, VisibilityFilters } from './actions' // 打印初始状态 console.log(store.getState()) // 每次 state 更新时,打印日志 // 注意 subscribe() 返回一个函数用来注销监听器 const unsubscribe = store.subscribe(() => console.log(store.getState()) ) // 发起一系列 action store.dispatch(addTodo('Learn about actions')) store.dispatch(addTodo('Learn about reducers')) store.dispatch(addTodo('Learn about store')) store.dispatch(toggleTodo(0)) store.dispatch(toggleTodo(1)) store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED)) // 停止监听 state 更新 unsubscribe(); ``` ## 异步 Action 1. 使用中间件redux-thunk ,redux-logger ``` // 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 日志 // ) // ) const middlewares = [thunkMiddleware,loggerMiddleware] const store = createStore( rootReducer, applyMiddleware(...middlewares) ) store.dispatch(selectSubreddit('reactjs')) console.log("state.......",store.getState()); store .dispatch(fetchPosts('reactjs')) // fetchPosts 为异步action .then(() => console.log(store.getState()) ) ``` ``` // 这是 异步的 action creator(同上面的 action creator 一样,都是一个函数,但是返回值是一个函数,不是一个 action (对象) export const fetchPosts = (subreddit)=> (dispatch,getState,extraArgument) =>{ console.log(dispatch) console.log(getState) console.log(extraArgument) //return fetch(`http://www.subreddit.com/r/${subreddit}.json`) //外网被屏蔽 return fetch(`https://api.apiopen.top/getJoke?page=1&count=2&type=video`) .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。 console.log("结果请求成功", json); dispatch(receivePosts(subreddit, json)) }) } // 等价上面函数 // 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`) // return fetch(`https://api.apiopen.top/getJoke?page=1&count=2&type=video`) // .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。 // console.log("结果请求成功", json); // dispatch(receivePosts(subreddit, json)) // }) // } // } ``` ## react-redux React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。 ### UI 组件 - 只负责 UI 的呈现,不带有任何业务逻辑 - 没有状态(即不使用this.state这个变量) - 所有数据都由参数(this.props)提供 - 不使用任何 Redux 的 API ### 容器组件 容器组件的特征恰恰相反。 - 负责管理数据和业务逻辑,不负责 UI 的呈现 - 带有内部状态 - 使用 Redux 的 API ### connect() - mapStateToProps?: Function - mapDispatchToProps?: Function | Object - mergeProps?: Function - options?: Objec - 返回值:The return of connect() is a **wrapper function** that takes your component and returns a wrapper component with the additional props it injects. #### mapStateToProps?: (state, ownProps?) => Object ``` const mapStateToProps = (state, ownProps) => ({ todo: state.todos[ownProps.id], }) ``` #### mapDispatchToProps?: Object | (dispatch, ownProps?) => Object ``` // binds on component re-rendering