├── .DS_Store ├── .babelrc ├── .gitignore ├── README.md ├── content ├── index.bundle.js └── index.html ├── package.json ├── src ├── App.js ├── components │ ├── CartItem.js │ ├── Footer.js │ ├── Header.js │ ├── Main.js │ └── Provider.js ├── index.js ├── main.scss ├── mock │ └── data.js └── stores │ └── AppState.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cqm1994617/react-mobx-cart/35e085eacccd78248d673dae18691f99f556e9bb/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-1" 6 | ], 7 | "plugins": [ 8 | "transform-decorators-legacy", 9 | "syntax-async-functions", 10 | "transform-async-to-generator", 11 | [ 12 | "import", 13 | { 14 | "style": "css", 15 | "libraryName": "antd-mobile" 16 | } 17 | ] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.iml 3 | .idea 4 | .gradle 5 | local.properties 6 | 7 | # node.js 8 | node_modules/ 9 | npm-debug.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-mobx-cart 2 | 3 | react+mobx的简单示例。 4 | 5 | [demo](https://cqm1994617.github.io/react-mobx-cart/content/index.html) 6 | 7 | 根目录下运行npm install -------------------------------------------------------------------------------- /content/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-start-kit", 3 | "version": "1.0.0", 4 | "description": "react脚手架", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:cqm1994617/react-start-kit.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/cqm1994617/react-start-kit/issues" 18 | }, 19 | "homepage": "https://github.com/cqm1994617/react-start-kit#readme", 20 | "dependencies": { 21 | "antd": "^2.6.0", 22 | "antd-mobile": "^0.9.13", 23 | "mobx": "^2.6.3", 24 | "mobx-react": "^3.5.9", 25 | "mobx-react-devtools": "^4.2.9", 26 | "nuka-carousel": "^2.0.4", 27 | "react": "^15.4.0", 28 | "react-dom": "^15.4.0", 29 | "react-router": "^3.0.2" 30 | }, 31 | "devDependencies": { 32 | "babel-core": "^6.18.2", 33 | "babel-eslint": "^7.1.1", 34 | "babel-loader": "^6.2.8", 35 | "babel-plugin-import": "^1.1.0", 36 | "babel-plugin-syntax-async-functions": "^6.13.0", 37 | "babel-plugin-transform-async-to-generator": "^6.16.0", 38 | "babel-plugin-transform-decorators": "^6.13.0", 39 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 40 | "babel-plugin-transform-regenerator": "^6.16.1", 41 | "babel-polyfill": "^6.16.0", 42 | "babel-preset-es2015": "^6.18.0", 43 | "babel-preset-react": "^6.16.0", 44 | "babel-preset-stage-1": "^6.16.0", 45 | "css-loader": "^0.26.0", 46 | "eslint": "^3.10.2", 47 | "jsx-loader": "^0.13.2", 48 | "node-sass": "^3.13.0", 49 | "sass-loader": "^4.0.2", 50 | "style-loader": "^0.13.1", 51 | "webpack": "^1.13.3", 52 | "webpack-dev-server": "^1.16.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppState from './stores/AppState'; 3 | import Provider from './components/Provider'; 4 | import Header from './components/Header'; 5 | import Main from './components/Main'; 6 | import Footer from './components/Footer'; 7 | import {observer} from 'mobx-react'; 8 | import './main.scss'; 9 | 10 | const store = new AppState(); 11 | 12 | @observer 13 | export default class App extends React.Component { 14 | 15 | render() { 16 | return ( 17 | 18 |
19 |

React+Mobx购物车示例

20 |
21 |
22 |
24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/CartItem.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import {observer} from 'mobx-react'; 3 | 4 | @observer 5 | export default class CartItem extends React.Component { 6 | 7 | static contextTypes = { 8 | store: PropTypes.object, 9 | }; 10 | 11 | static propTypes = { 12 | data: PropTypes.object, 13 | }; 14 | 15 | render() { 16 | const { data } = this.props; 17 | const { store } = this.context; 18 | 19 | return ( 20 |
21 | 29 |
{data.name}
30 |
¥{data.price}
31 |
32 |
store.sub(data.id)}>-
33 | 36 |
store.add(data.id)}>+
37 |
38 |
¥{data.price * data.buyNum}
39 |
store.removeItem(data.id)}>删除
40 |
41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import {observer} from 'mobx-react'; 3 | 4 | const Footer = observer((props, {store}) => ( 5 |
6 |

合计: {store.totalPrice}元

7 |
8 | )); 9 | 10 | Footer.contextTypes = { 11 | store: PropTypes.object 12 | }; 13 | 14 | export default Footer; 15 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | const Header = observer((props, {store}) => ( 5 |
6 | 14 |
商品
15 |
单价
16 |
数量
17 |
小计
18 |
操作
19 |
20 | )); 21 | 22 | Header.contextTypes = { 23 | store: PropTypes.object 24 | }; 25 | 26 | export default Header; -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import CartItem from './CartItem'; 3 | import {observer} from 'mobx-react'; 4 | 5 | @observer 6 | export default class Main extends React.Component { 7 | 8 | static contextTypes = { 9 | store: PropTypes.object, 10 | }; 11 | 12 | render() { 13 | const {store} = this.context; 14 | return ( 15 |
16 | {store.list.map((z, i) => )} 17 |
18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Provider.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | 3 | export default class Provider extends React.Component { 4 | 5 | static propTypes = { 6 | store: PropTypes.object.isRequired, 7 | }; 8 | 9 | static childContextTypes = { 10 | store: PropTypes.object, 11 | }; 12 | 13 | getChildContext() { 14 | return { 15 | store: this.props.store 16 | } 17 | } 18 | 19 | render() { 20 | return this.props.children; 21 | } 22 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | require('babel-polyfill'); 5 | ReactDOM.render(, document.getElementById('container')); -------------------------------------------------------------------------------- /src/main.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Microsoft YaHei'; 5 | outline: none; 6 | } 7 | 8 | h1 { 9 | margin: 20px 0; 10 | text-align: center; 11 | } 12 | 13 | input::-ms-clear{ 14 | display: none; 15 | } 16 | 17 | .head { 18 | padding: 15px; 19 | box-sizing: border-box; 20 | border-top: 1px solid #eee; 21 | border-bottom: 1px solid #eee; 22 | display: flex; 23 | } 24 | 25 | .head-item { 26 | flex: 1; 27 | text-align: center; 28 | } 29 | 30 | .container { 31 | width: 960px; 32 | margin: auto; 33 | border: 1px solid #ccc; 34 | } 35 | 36 | .cart-item { 37 | width: 100%; 38 | box-sizing: border-box; 39 | padding: 15px; 40 | border-bottom: 1px solid #eee; 41 | display: flex; 42 | text-align: center; 43 | & > .c-buyNum {} 44 | } 45 | 46 | .cart-item:last-of-type { 47 | border-bottom: none; 48 | } 49 | 50 | .cart-item-box { 51 | color: #777; 52 | flex: 1; 53 | word-break: break-all; 54 | line-height: 40px; 55 | } 56 | 57 | .c-name { 58 | flex: 2; 59 | } 60 | 61 | .c-delete:hover { 62 | cursor: pointer; 63 | } 64 | 65 | .c-buyNum { 66 | flex: 2; 67 | display: flex; 68 | justify-content: center; 69 | div { 70 | width: 40px; 71 | height: 40px; 72 | user-select:none; 73 | line-height: 38px; 74 | background-color: #fff; 75 | border: 1px solid #ccc; 76 | box-sizing: border-box; 77 | } 78 | div:hover { 79 | cursor: pointer; 80 | } 81 | input { 82 | height: 40px; 83 | width: 50px; 84 | color: #999; 85 | text-align: center; 86 | box-sizing: border-box; 87 | } 88 | } 89 | 90 | .footer { 91 | border-top: 1px solid #eee; 92 | padding: 15px; 93 | } -------------------------------------------------------------------------------- /src/mock/data.js: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | id: 100, 4 | name: "i7 7700k", 5 | type: "数码类产品", 6 | description: "CPU", 7 | price: 2400, 8 | buyNum: 1, 9 | }, 10 | { 11 | id: 101, 12 | name: "R7 1800X", 13 | type: "数码类产品", 14 | description: "CPU", 15 | price: 3999, 16 | buyNum: 2, 17 | }, 18 | { 19 | id: 102, 20 | name: "GTX1080TI", 21 | type: "数码类产品", 22 | description: "显卡", 23 | price: 5999, 24 | buyNum: 1, 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /src/stores/AppState.js: -------------------------------------------------------------------------------- 1 | import {observable, useStrict, action, computed} from 'mobx'; 2 | import {data} from '../mock/data'; 3 | 4 | useStrict(true); 5 | 6 | function findById(list, id) { 7 | return list.filter(z => z.id === id)[0]; 8 | } 9 | 10 | const dataList = data.map((z) => { 11 | return { 12 | checked: true, 13 | ...z 14 | } 15 | }); 16 | 17 | export default class AppState { 18 | 19 | @observable list = dataList; 20 | @observable checkedAll = true; 21 | 22 | @action removeItem = (id) => { 23 | this.list.forEach((item, i) => { 24 | if (item.id === id) { 25 | this.list.splice(i, 1); 26 | } 27 | }); 28 | }; 29 | 30 | @action add = (id) => { 31 | this.list.forEach(item => item.id === id && item.buyNum++); 32 | }; 33 | 34 | @action sub = (id) => { 35 | this.list.forEach(item => (item.id === id && item.buyNum > 0) && item.buyNum--); 36 | }; 37 | 38 | @action onChecked = (id) => { 39 | this.list.forEach(item => { 40 | if (item.id === id) { 41 | item.checked ? this.checkedAll = false : null; 42 | item.checked = !item.checked; 43 | } 44 | }); 45 | !this.list.some((item) => item.checked === false) ? this.checkedAll = true : null; 46 | }; 47 | 48 | @action onCheckedAll = () => { 49 | this.checkedAll = !this.checkedAll; 50 | this.checkedAll ? this.list.forEach(item => item.checked = true) : this.list.forEach(item => item.checked = false); 51 | }; 52 | 53 | @computed get totalPrice() { 54 | let total = 0; 55 | this.list.forEach((item, i) => { 56 | if (item.checked) { 57 | total += this.list[i].buyNum * this.list[i].price; 58 | } 59 | }); 60 | return total; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: [ 6 | 'babel-polyfill', 7 | path.resolve(__dirname, 'src/index.js') 8 | ], 9 | output: { 10 | path: path.resolve(__dirname, 'content'), 11 | filename: 'index.bundle.js', 12 | }, 13 | 14 | resolve: { 15 | modulesDirectories: [ 16 | 'src', 17 | 'node_modules', 18 | path.join(__dirname, '../node_modules') 19 | ], 20 | extensions: ['', '.web.js', '.json', '.js'] 21 | }, 22 | 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.css$/, 27 | loader: 'style-loader!css-loader', 28 | }, 29 | { 30 | test: /\.js$/, 31 | loaders: ['babel'], 32 | include: path.join(__dirname, 'src') 33 | }, 34 | { 35 | test: /\.scss$/, 36 | loader: 'style!css!sass?sourceMap', 37 | }, 38 | { 39 | test: /\.(png|jpg)$/, 40 | loader: 'url-loader?limit=8192', 41 | }, 42 | ] 43 | }, 44 | 45 | plugins:[ 46 | new webpack.DefinePlugin({ 47 | 'process.env': { 48 | NODE_ENV:JSON.stringify('production'), 49 | }, 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), //热替换 52 | ], 53 | 54 | devServer:{ 55 | contentBase:'./content', 56 | hot: true, 57 | inline: true, //热替换 58 | historyApiFallback: true, 59 | port: 3000, 60 | }, 61 | }; --------------------------------------------------------------------------------