├── .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 |
23 |
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 | };
--------------------------------------------------------------------------------