├── .gitignore ├── README.md ├── step-01 ├── .babelrc ├── README.md ├── index.html ├── package.json ├── server.js ├── src │ ├── App.js │ ├── favicon.ico │ ├── index.js │ └── template.html ├── webpack-dev-config.js └── webpack-pro-config.js ├── step-02 ├── .babelrc ├── README.md ├── package.json ├── server.js ├── src │ ├── favicon.ico │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ ├── images │ │ ├── big.png │ │ └── small.png │ ├── index.html │ ├── index.js │ ├── js │ │ ├── components │ │ │ ├── Animation │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── BigComponent │ │ │ │ ├── BigOne │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ ├── BigTwo │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ └── index.js │ │ │ ├── CommonTitle │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── CssTest │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Tab │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ └── index.js │ │ └── containers │ │ │ └── App.js │ ├── styles │ │ ├── animations.scss │ │ ├── app.scss │ │ ├── font.scss │ │ ├── mixins.scss │ │ └── normalize.scss │ └── webpack-public-path.js ├── webpack-dev-config.js └── webpack-pro-config.js ├── step-03 ├── .babelrc ├── README.md ├── package.json ├── server.js ├── src │ ├── favicon.ico │ ├── fonts │ │ ├── dudu.eot │ │ ├── dudu.svg │ │ ├── dudu.ttf │ │ └── dudu.woff │ ├── images │ │ ├── big.png │ │ └── small.png │ ├── index.html │ ├── js │ │ ├── components │ │ │ ├── FastNav │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Header │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Message │ │ │ │ ├── Item │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Nav │ │ │ │ ├── NavLink │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── SelectMenu │ │ │ │ ├── ConList │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ ├── TopList │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.scss │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── ShopList │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Swipe │ │ │ │ └── index.js │ │ │ ├── Title │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ └── index.js │ │ ├── containers │ │ │ ├── 404.js │ │ │ ├── App.js │ │ │ ├── Coupon │ │ │ │ ├── Detail.js │ │ │ │ └── index.js │ │ │ ├── Home.js │ │ │ ├── Root.js │ │ │ ├── Shop │ │ │ │ ├── Detail.js │ │ │ │ └── index.js │ │ │ ├── Tour │ │ │ │ └── index.js │ │ │ ├── User │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── index.js │ │ └── routes.js │ ├── styles │ │ ├── animations.scss │ │ ├── antdStyleReset.scss │ │ ├── app.scss │ │ ├── font.scss │ │ ├── mixins.scss │ │ └── normalize.scss │ └── webpack-public-path.js ├── webpack-dev-config.js └── webpack-pro-config.js └── step-04 ├── .babelrc ├── README.md ├── package.json ├── server.js ├── src ├── favicon.ico ├── fonts │ ├── dudu.eot │ ├── dudu.svg │ ├── dudu.ttf │ └── dudu.woff ├── images │ ├── big.png │ └── small.png ├── index.html ├── js │ ├── actions │ │ ├── CounterActions.js │ │ ├── PostActions.js │ │ ├── TimerActions.js │ │ ├── TodoActions.js │ │ └── actionsTypes.js │ ├── components │ │ ├── Counter │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── Nav │ │ │ ├── NavLink │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── Post │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── ScrollBox │ │ │ └── index.js │ │ ├── Timer │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── Todos │ │ │ ├── Footer │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── Header │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── MainSection │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── TodoItem │ │ │ │ ├── index.js │ │ │ │ ├── index.scss │ │ │ │ └── touchFunc.js │ │ │ ├── TodoTextInput │ │ │ │ ├── index.js │ │ │ │ └── index.scss │ │ │ ├── index.js │ │ │ └── index.scss │ │ └── index.js │ ├── containers │ │ ├── 404.js │ │ ├── App.js │ │ ├── Coupon │ │ │ ├── Detail.js │ │ │ └── index.js │ │ ├── Home.js │ │ ├── Root.js │ │ ├── Tour │ │ │ └── index.js │ │ ├── User │ │ │ └── index.js │ │ └── index.js │ ├── index.js │ ├── reducers │ │ ├── counter.js │ │ ├── counterAsync.js │ │ ├── index.js │ │ ├── posts.js │ │ ├── showCongratulation.js │ │ ├── timer.js │ │ └── todos.js │ ├── routes.js │ ├── sagas │ │ ├── index.js │ │ ├── posts.js │ │ └── synchronous.js │ └── store │ │ ├── configureStore.dev.js │ │ ├── configureStore.js │ │ └── configureStore.prod.js ├── styles │ ├── animations.scss │ ├── antdStyleReset.scss │ ├── app.scss │ ├── font.scss │ ├── mixins.scss │ └── normalize.scss └── webpack-public-path.js ├── webpack-dev-config.js └── webpack-pro-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | *.log 4 | npm-debug* 5 | node_modules 6 | dist 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [react.js](https://github.com/facebook/react) 是一个构建用户界面的javascript库,它因**单向数据绑定**和**虚拟 DOM** 两大特点在前端界大放异彩。 2 | 因为它解决了当下网页性能陷入的瓶颈————由于直接操作DOM导致页面性能损失很大,而虚拟DOM避免了直接操作DOM(Jquery 是一个典型的操作DOM的库,所以 React 开发中,我们尽量不要使用Jquery)。再加上 React 单向数据绑定的特点使得业务逻辑更加清晰可控。 3 | 另外,`react.js` 是大名鼎鼎的 Facebook 一手打造维护,目前其在 github 上已有超过5万的 Star 量。 4 | 同时,react 社区也异常活跃,各种基于 React 的非常优秀的库和框架层出不穷,进而推动了 react 的流行和壮大,围绕 React 为核心的生态圈已悄然成型。 5 | ## 最新通知(2018/1/11) 6 | hello,everybody,自React-Study项目推出以来,多多少少帮助了不少入坑React的新人同学,自其上次更新已一年有余,然而并没有一个实际开发的项目演示,故今天推出一个 [React16 + next.js 4 + antd-mobile2 + redux 的服务端渲染架构](https://github.com/minooo/react-ssr),其已经成功运用于公司的一个项目。欢迎与各位交流学习,2018,让我们进步更多! 7 | ## 版本维护日志 8 | - 精简了热重载[HMR]配置; 9 | 修复了step-04 导航"复杂"上的示例在苹果手机上异常的问题。 10 | ——2016/12/22 11 | - 修复了部分电脑运行step-04出错的问题; 12 | 修复了脚手架在应用react最新版本 15.4.1后,无法在微信上显示的问题。 13 | ——2016/12/18 14 | 15 | ## 认识React生态圈 16 | 17 | 用阮一峰老师的话说就是: **React 已不是一个库,也不是一个框架,而是一个庞大的体系。想要发挥它的威力,整个技术栈都要配合它改造。你要学习一整套解决方案,从后端到前端,都是全新的做法。** 时至今日,围绕以React为核心的技术栈也日益成型,它主要包含: 18 | 19 | - React, 20 | - npm 21 | - js打包工具(如:webpack) 22 | - ES6 23 | - Routing 24 | - Redux 25 | 26 | **你不需要把这些都学完才去使用 React. 只需要在你遇到问题需要解决的时候, 才进入相关的学习。** 27 | 28 | ## 学习 React 生态圈 29 | 学习 React 生态圈是一个综合应用React技术栈的过程,这也是最接近我们实际开发运用React的情境,为此,笔者特地根据以往React开发经验,精心制作了[React-Study](https://github.com/minooo/React-Study)系列React技术栈学习模板,以实际项目开发情境为目标,从最简单的hello,world开始,通过逐步升级配置,来学习React生态圈并最终应用到公司项目中。 30 | 31 | React-Study 系列模板主要包含以下四部分 32 | 33 | - [step-01](https://github.com/minooo/React-Study/tree/master/step-01)(已完成) 34 | 这部分就是基础的hello,world模板,前面说了,这系列模板是以实际项目开发情境为目标而构建的,虽说是 hello,world的示例,但是它综合应用了 React+webpack+es2015+npm ,并且分为开发模式(开启了热替换和sourcemap)和产品模式(也就是打包,开启了代码压缩等优化) 35 | 36 | - [step-02](https://github.com/minooo/React-Study/tree/master/step-02)(已完成) 37 | step-02 是在 step-01的基础上添加额外配置完成的,这一部分添加了 样式,字形,图片,等加载器配置。并初步展示了在项目实践中,React技术栈的一个合理的目录结构应该是怎样的。由于应用了CSSModules以及相关的辅助插件,CSS的语法更加便利简洁,这些在项目的组件样式中都有体现。同时,也展示了在ES6下,React组件相关写法,以及标准语法的规范的推荐。总之,React带你走进组件化的美好 38 | 39 | - [step-03](https://github.com/minooo/React-Study/tree/master/step-03)(已完成) 40 | step-03 是在 step-02 的基础上开发的,step-03 主要围绕添加 react-router 进行配置,以及在react移动端开发中,强烈推荐使用antd-mobile 这个特别符合我国国情的react组件库。本模板延续组件化的思想,以及样式的模块化(cssModules), 并以真实项目实践写了几个简单的组件,包括底部导航,好店列表,以及下拉菜单等。 目的就是展示下,在真实项目中,组件化的思想是如何实践的。 41 | 42 | - [step-04](https://github.com/minooo/React-Study/tree/master/step-04) (已完成) 43 | step-04 是在 step-03 的基础上添加额外配置完成,为了更好的解决react中组件之间的数据传递, 44 | 此模板引入了[redux](https://github.com/reactjs/redux),redux 的三大核心法宝就是 `action`, `reducer`, `store`, 45 | redux入门推荐教程 [redux-tutorial 使用教程](https://github.com/react-guide/redux-tutorial-cn/blob/master/00_introduction.js) [redux 入门教程](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html); 46 | 同时为了更优雅的管理redux的异步操作,经过再三对比和考虑,本模板使用了[redux-saga](https://github.com/yelouafi/redux-saga/),用来替代[redux-thunk](https://github.com/gaearon/redux-thunk)。 47 | [redux-sage中文文档(繁体,同步)](https://neighborhood999.github.io/redux-saga/) 48 | 49 | ## 启动React生态圈 50 | 51 | ##### 克隆项目 52 | git clone https://github.com/minooo/React-Study.git 53 | 54 | ##### 进入目录(比如step-01) 55 | cd step-01 56 | 57 | ##### 安装依赖 58 | npm install 59 | 60 | ##### 启动开发模式(运行 npm run build,即可将项目打包) 61 | npm start 62 | 63 | ##### 启动就绪后,打开浏览器,输入 http://localhost:3000/ ,看到惊喜了吗? 64 | 65 | ## 常见问题说明。 66 | 67 | - 请保证电脑安装的 [node](http://nodejs.cn/) 版本在 **6.0以上** ,如果你“不幸”安装了4.0版本, 68 | 请先将其卸载,再安装6.0+版本(目前node官网已有7.2版本[英文官网](https://nodejs.org/en/),请尽量安装最新版) 69 | 70 | - 很多新手朋友可能事先跟着react官网实例做了一些练习,用的都是 es5 的语法。 71 | 而本项目代码采用的都是 es6 的语法,这也是react官网推荐的。如果你对es6语法不太熟悉 72 | 可以看下[React es5---es6 写法对照表](http://bbs.reactnative.cn/topic/15/react-react-native-%E7%9A%84es5-es6%E5%86%99%E6%B3%95%E5%AF%B9%E7%85%A7%E8%A1%A8) 73 | 同时也建议你花[30分钟,快速了解ES6语法](https://segmentfault.com/a/1190000004365693) 74 | 当然,本项目所有组件示例也可以当作你学习es6写法的参考。 75 | 76 | - 如果你有使用webstorm作为你的IDE,初次运行本项目,软件可能会提示你 `Add watcher` , 77 | 由于本项目已配置好了一整套的编译流程,所以不要此类协助,直接忽略取消即可;另外由于 78 | 项目代码用的都是JSX语法,webstorm 可能默认的解析js语法是es5, 79 | 所以此时你会看到文件都是“一片红”错误标注,如下改下解析设置就行了: 80 | `File` -> `Settings` -> `Languages & Frameworks` -> `JavaScript` 81 | 选择右侧面板中的下拉框,将选项 `JavaScript languaga version` 的值改为 `React JSX` 即可 82 | 83 | - 如果你在学习本项目遇到问题,请加群交流: [419922267](http://jq.qq.com/?_wv=1027&k=2FnzuGM) 84 | 85 | # License 86 | MIT 87 | 88 | 89 | -------------------------------------------------------------------------------- /step-01/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { 5 | "loose": true 6 | } 7 | }], 8 | "react", 9 | "stage-0" 10 | ], 11 | "plugins": [ 12 | "react-hot-loader/babel", 13 | ["transform-runtime", { 14 | "helpers": false, 15 | "polyfill": false, 16 | "regenerator": true, 17 | "moduleName": "babel-runtime" 18 | }] 19 | ] 20 | } -------------------------------------------------------------------------------- /step-01/README.md: -------------------------------------------------------------------------------- 1 | # step-01 目前已完成,你可以完整运行 2 | 3 | 本模板综合运用了react+webpack+es2015。 4 | 本模板是以实际应用为目的,由基础配置向高级配置逐步递进。 5 | 这是React-Study的第一个版本,React版本为15.3.1,该版本完全满足你单纯学习React的需求 6 | 本模板有两种模式: 7 | 8 | 1. 开发模式————有热替换功能,可以将错误信息捕捉并高亮显示到页面。 9 | 1. 生产模式————将你的资源进行打包,压缩等。 10 | 11 | ## 核心摘要 12 | 13 | - [x] [Webpack](https://webpack.github.io) 14 | - [x] [React](https://facebook.github.io/react/) 15 | - [x] [Babel](https://babeljs.io/) 16 | 17 | ## [DEMO](https://raw.githubusercontent.com/minooo/test/master/react-study-01.gif) 18 | 点击上方DEMO预览 19 | 20 | ## 安装所有包 21 | 22 | ``` 23 | $ npm install 24 | ``` 25 | 26 | ## 启动 开发模式 27 | 28 | ``` 29 | $ npm start 30 | ``` 31 | 32 | ## 启动 生产模式(打包输出) 33 | 34 | ``` 35 | $ npm run build 36 | ``` 37 | 38 | ## 本模板用到的 __所有包__ 的相关简要说明 39 | 40 | ####[react.js](https://facebook.github.io/react/index.html) [必需] 41 | > React是用来构建用户界面的js库,属于view层。 42 | 它有两大特点:1,单向数据绑定;2,虚拟DOM 43 | 安装:`npm install --save react` 44 | 45 | --- 46 | 47 | ####[react-dom.js](https://npm.taobao.org/package/react-dom) [必需] 48 | > react.js 主要用来创建元素和组件,当你想在html中渲染你的组件的时候, 49 | 你还得需要react-dom.js。同时,react-dom.js依赖于react.js。 50 | 安装:`npm install --save react-dom` 51 | 52 | --- 53 | 54 | ####[webpack](https://npm.taobao.org/package/react-dom) [必需] 55 | > 于人而言,尤其是当开发大型项目时,每个包每个模块每个静态资源都应尽可能的条理清晰的罗列出来, 56 | 这样方便我们开发;于机器而言,就不需要这么“条理清晰”了,此时应最大限度的压缩优化这些资源, 57 | 如何把这些资源模块“杂糅”在一起,这就是webpack要做的。 58 | 安装:`npm install --save-dev webpack` 59 | 备注:webpack 2.0 即将发布 60 | webpack 最基本的启动webpack命令 61 | webpack -w 提供watch方法,实时进行打包更新 62 | webpack -p 压缩混淆脚本,这个非常非常重要! 63 | webpack -d 生成map映射文件,告知哪些模块被最终打包到哪里了,方便调试 64 | webpack --progress 显示打包进程,百分比显示 65 | webpack --config XXX.js //使用另一份配置文件(比如webpack.config2.js)来打包 66 | webpack --colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤 67 | webpack --profile 输出性能数据,可以看到每一步的耗时 68 | webpack --display-error-details 方便出错时能查阅更详尽的信息(比如 webpack 寻找模块的过程),从而更好定位到问题。 69 | webpack --display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块 70 | [webpack入门配置](https://segmentfault.com/a/1190000005089993) 71 | 72 | --- 73 | 74 | ####[webpack-dev-middleware](https://npm.taobao.org/package/webpack-dev-middleware) [开发需要] 75 | > 它是一个用来组织包装webpack使其变成中间件的容器。(中间件的用途就是在输入和输出的过程中加工的一种手段) 76 | webpack本身只负责打包编译,webpack-dev-server是协助我们开发的服务器,这个服务器底层是靠express操作的。 77 | 我们的页面如何在这个服务器上更新呢,首先是取得webpack打包好的资源,这就需要在`请求`到`响应`的过程中通过 78 | express的中间件取得资料, 而方法就是通过webpack-dev-middleware来实现。 79 | 这个中间件只在开发环境中使用,切忌用在生产环境中。 80 | 安装:`npm install --save-dev webpack-dev-middleware` 81 | 82 | ####这个中间件有两点好处: 83 | 84 | 1. 直接在内存中操作文件,而非磁盘中。这样处理速度更快。 85 | 1. 在监视(watch)模式下,如果文件改变,中间件立即停止提供之前的bundle,并且会延迟 86 | 请求回应,直到新的编译完成,如此一来,文件修改后,你可以直接刷新页面,而不用等待编译。 87 | 88 | --- 89 | 90 | ####[webpack-hot-middleware](https://npm.taobao.org/package/webpack-hot-middleware) [开发需要] 91 | > `webpack-dev-middleware` + `webpack-hot-middleware` 即可让我们用 `express` 92 | 定制一个有热替换功能的 `webpack` 开发服务器。 93 | 安装:`npm install --save-dev webpack-hot-middleware` 94 | 95 | --- 96 | 97 | ####[babel-core](https://npm.taobao.org/package/babel-core) [必需] 98 | > Babel是一个转换编译器,它能将ES6转换成可以在浏览器中运行的代码。 99 | 作为下一代javascript语言标准,请拥抱ES6(ES2015)吧!`babel-core` 是Babel编译器的核心。 100 | 安装:`npm install --save-dev babel-core` 101 | 102 | --- 103 | 104 | ####[babel-loader](https://npm.taobao.org/package/babel-loader) [必需] 105 | > loader 用于转换应用程序的资源文件,他们是运行在nodejs下的函数, 106 | 使用参数来获取一个资源的来源并且返回一个新的来源针对webpack的babel加载器。 107 | `babel-loader` 就是告诉webpack去加载我们写的使用了es6语法的js文件。 108 | 安装:`npm install --save-dev babel-loader` 109 | 110 | --- 111 | 112 | #### [babel-runtime](http://babeljs.io/docs/plugins/transform-runtime/#why) [强烈推荐] 113 | > Babel默认只转换新的JavaScript语法,而不是转换新的API, 114 | 比如Iterator、Generator、Set、Maps、Proxy、Reflect,Symbol、Promise等全局对象, 115 | 以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。 116 | 举例来说,ES6在Array对象上新增了Array.from方法。 117 | [延伸阅读,强烈推荐](https://segmentfault.com/a/1190000006930013?utm_source=tuicool&utm_medium=referral) 118 | 安装:`npm install --save babel-runtime` 119 | 120 | #### [babel-plugin-transform-runtime](http://babeljs.io/docs/plugins/transform-runtime/#why) [开发需要] 121 | > 和上面的 `babel-runtime` 搭配使用 122 | 安装:`npm install --save-dev babel-plugin-transform-runtime` 123 | 124 | ####[babel-preset-latest](http://babeljs.io/docs/plugins/preset-latest/) [必需] 125 | > es2015,es2016,es2017转码规则。为所有es6插件所设置的babel预设, 126 | 有了它,诸如,es6的箭头函数,类,等等语法特性才能向es5转换。 127 | 安装:`npm install --save-dev babel-preset-latest` 128 | 129 | --- 130 | 131 | ####[babel-preset-react](https://github.com/babel/babel) [必需] 132 | > react转码规则。为所有react插件所设置的babel预设。有了它,才能识别转译jsx语法等。 133 | 安装:`npm install --save-dev babel-preset-react` 134 | 135 | --- 136 | 137 | ####[react-hot-loader](https://npm.taobao.org/package/react-hot-loader) [开发需要] 138 | > 可以使react组件在浏览器上实时更新而无需手动刷新。 139 | 安装:`npm install --save-dev react-hot-loader@3.0.0-beta.3` 140 | 备注:用的是3.0最新版本,这版本很强大。 141 | 142 | --- 143 | 144 | ####[babel-preset-stage-X](https://npm.taobao.org/package/babel-preset-stage-0) [必需] 145 | > ES7不同阶段语法提案的转码规则(共有4个阶段),选装**一个** 146 | 在进行实际开发时,可以根据需要来设置对应的stage。如果省事懒得折腾,一般设置为stage-0即可。 147 | npm install --save-dev babel-preset-stage-0 148 | npm install --save-dev babel-preset-stage-1 149 | npm install --save-dev babel-preset-stage-2 150 | npm install --save-dev babel-preset-stage-3 151 | [stage-X详解](http://www.cnblogs.com/flyingzl/p/5501247.html) 152 | 153 | --- 154 | 155 | ####[redbox-react](https://github.com/KeywordBrain/redbox-react) [开发需要] 156 | > 这个插件将会以一个非常优雅的方式(看demo演示)将你的错误呈现在页面上,这样就省去了查看console.log的麻烦; 157 | 158 | --- 159 | 160 | ####[html-webpack-plugin](https://npm.taobao.org/package/html-webpack-plugin) [小工具] 161 | > 一个服务于webpack打包资源的简易的HTML文件生成器,它可以动态生成HTML 162 | 之所以要动态生成,主要是希望webpack在完成前端资源打包以后,自动将打包后的资源路径和版本号写入HTML中,达到自动化的效果 163 | 安装:`npm install --save-dev html-webpack-plugin` 164 | 165 | --- 166 | 167 | ####[express](https://npm.taobao.org/package/express) [开发需要] 168 | > 基于 Node.js 平台,快速、开放、极简的 web 开发框架。 169 | 在这里用于配置开发服务器。 170 | 安装:`npm install --save-dev express` 171 | 172 | --- 173 | 174 | ####[rimraf](https://npm.taobao.org/package/rimraf) [小工具] 175 | > 一个基于node的深层删除工具(楼主曾经用window自带的删除命令删node_modules花了十分钟,用这个删十秒搞定!) 176 | 安装:`npm install --save-dev rimraf` 177 | 178 | --- 179 | 180 | 181 | -------------------------------------------------------------------------------- /step-01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 开发模式 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /step-01/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-01", 3 | "version": "1.0.0", 4 | "description": "react-base", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "clean": "rimraf dist", 9 | "build:webpack": "webpack --config webpack-pro-config.js --progress --colors", 10 | "build": "npm run clean && npm run build:webpack" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "es6", 15 | "webpack" 16 | ], 17 | "author": "minooo", 18 | "license": "MIT", 19 | "dependencies": { 20 | "babel-runtime": "^6.20.0", 21 | "react": "^15.4.1", 22 | "react-dom": "^15.4.1" 23 | }, 24 | "devDependencies": { 25 | "babel-core": "^6.10.4", 26 | "babel-loader": "^6.2.4", 27 | "babel-plugin-transform-runtime": "^6.15.0", 28 | "babel-preset-latest": "^6.16.0", 29 | "babel-preset-react": "^6.11.1", 30 | "babel-preset-stage-0": "^6.5.0", 31 | "express": "^4.14.0", 32 | "html-webpack-plugin": "^2.22.0", 33 | "react-hot-loader": "^3.0.0-beta.6", 34 | "redbox-react": "^1.2.10", 35 | "rimraf": "^2.5.3", 36 | "webpack": "^1.13.1", 37 | "webpack-dev-middleware": "^1.6.1", 38 | "webpack-hot-middleware": "^2.12.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /step-01/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by minooo on 2016/7/15. 3 | */ 4 | var path = require('path'); 5 | var webpack = require('webpack'); 6 | var webpackDevMiddleware = require('webpack-dev-middleware'); 7 | var webpackHotMiddleware = require('webpack-hot-middleware'); 8 | var config = require('./webpack-dev-config'); 9 | 10 | var app = new (require('express'))(); 11 | var port = 3000; 12 | // 监听的端口是3000,届时可以在在浏览器输入 localhost:3000 直接访问 13 | 14 | var compiler = webpack(config); 15 | app.use(webpackDevMiddleware(compiler, { 16 | noInfo: true, 17 | // 如果false,将会给你列出一大堆无聊的信息。 18 | 19 | publicPath: config.output.publicPath, 20 | stats: { 21 | colors: true 22 | } 23 | })); 24 | app.use(webpackHotMiddleware(compiler)); 25 | 26 | app.get('*', function(req, res) { 27 | res.sendFile(path.join(__dirname, 'index.html')); 28 | }); 29 | 30 | app.listen(port, function(error) { 31 | if (error) { 32 | console.error(error) 33 | } else { 34 | console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) 35 | } 36 | }); -------------------------------------------------------------------------------- /step-01/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Counter extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { counter: 0 }; 7 | this.interval = setInterval(() => this.tick(), 1000); 8 | } 9 | 10 | tick() { 11 | this.setState({ 12 | counter: this.state.counter + this.props.increment 13 | }); 14 | } 15 | 16 | componentWillUnmount() { 17 | clearInterval(this.interval); 18 | } 19 | 20 | render() { 21 | return ( 22 |

23 | Counter ({this.props.increment}): {this.state.counter} 24 |

25 | ); 26 | } 27 | } 28 | 29 | class Lists extends Component { 30 | state = { 31 | lists: [ 32 | {name: 'minooo'}, 33 | {name: '刘德华'}, 34 | {name: '郭富城'}, 35 | {name: '黎明'} 36 | ] 37 | }; 38 | 39 | render() { 40 | const {lists} = this.state; 41 | return ( 42 | 49 | ) 50 | } 51 | } 52 | 53 | export default class App extends Component { 54 | render() { 55 | return ( 56 |
57 |

hello, word

58 | 59 | 60 |
61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /step-01/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-01/src/favicon.ico -------------------------------------------------------------------------------- /step-01/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import App from './App' 4 | 5 | render( 6 | , 7 | document.getElementById('app') 8 | ); 9 | 10 | // 为了降低入门难度,以上配置精简了下,可以实现基本的 实时热更新 11 | // 如果想实现更为高级的 无刷新局部替换 ,可以参考 step-02 以及之后的版本 12 | // 当然你可以根据实际需要,选择合适的热替换方案 13 | -------------------------------------------------------------------------------- /step-01/src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 10 | 11 | 12 |
13 | 18 | 19 | -------------------------------------------------------------------------------- /step-01/webpack-dev-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 开发模式下的webpack配置 3 | * 在整个项目开发过程中,几乎99%的时间都是在这个模式下进行的 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | 7 | const path = require('path'); 8 | const webpack = require('webpack'); 9 | 10 | module.exports = { 11 | 12 | devtool: 'cheap-module-eval-source-map', 13 | // devtool 指明了sourcemap的生成方式,它有七个选项,具体请参考 https://segmentfault.com/a/1190000004280859 14 | // sourcemap 的作用就是为调试代码提供便利 15 | // cheap-module-eval-source-map 绝大多数情况下都会是最好的选择,这也是下版本 webpack 的默认选项。 16 | 17 | entry: [ 18 | 'react-hot-loader/patch', 19 | 'webpack-hot-middleware/client?reload=true', 20 | // 这里reload=true的意思是,如果碰到不能hot reload的情况,就整页刷新。 21 | path.resolve(__dirname, 'src/index.js') 22 | ], 23 | // 页面入口文件配置 24 | 25 | output: { // output项告诉webpack怎样存储输出结果以及存储到哪里 26 | filename: 'bundle.js', 27 | 28 | path: path.join(__dirname, 'dist'), 29 | // 输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它 30 | // “path”仅仅告诉Webpack结果存储在哪里 31 | 32 | publicPath: 'http://localhost:3000/' 33 | //模板、样式、脚本、图片等资源对应的server上的路径 34 | // “publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。 35 | }, 36 | // 文件输出目录 37 | 38 | resolve: { 39 | extensions: ['', '.js', 'jsx'] 40 | }, 41 | // 实际就是自动添加后缀,默认是当成js文件来查找路径 42 | // 空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式 43 | 44 | module: { 45 | loaders: [ 46 | { 47 | test: /\.js$/, 48 | loaders: ['babel'], 49 | exclude: /node_modules/, 50 | include: __dirname 51 | } 52 | ] 53 | }, 54 | 55 | plugins: [ 56 | new webpack.HotModuleReplacementPlugin(), 57 | // 启用热替换,仅开发模式需要 58 | 59 | new webpack.NoErrorsPlugin() 60 | // 允许错误不打断程序,,仅开发模式需要 61 | 62 | ] 63 | }; -------------------------------------------------------------------------------- /step-01/webpack-pro-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 产品模式下的webpack配置 3 | * 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | 7 | var path = require('path'); 8 | 9 | var webpack = require('webpack'); 10 | 11 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 12 | // webpack中生成HTML的插件, 13 | 14 | module.exports = { 15 | entry: { 16 | // 文件入口配置 17 | index: './src/index', 18 | vendor: [ 19 | 'react', 20 | 'react-dom' 21 | ] 22 | // 为了优化,切割代码,提取第三方库(实际上,我们将会引入很多第三方库) 23 | }, 24 | // 页面入口文件配置 25 | 26 | output: { 27 | // 文件输出配置 28 | 29 | path: path.join(__dirname, 'dist'), 30 | // 输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它 31 | 32 | publicPath: '', 33 | // 模板、样式、脚本、图片等资源对应的server上的路径 34 | 35 | filename: 'bundle.js' 36 | // 命名生成的JS 37 | }, 38 | 39 | plugins: [ 40 | new webpack.optimize.OccurrenceOrderPlugin(), 41 | // webapck 会给编译好的代码片段一个id用来区分 42 | // 而这个插件会让webpack在id分配上优化并保持一致性。 43 | // 具体是的优化是:webpack就能够比对id的使用频率和分布来得出最短的id分配给使用频率高的模块 44 | 45 | new webpack.optimize.UglifyJsPlugin({ 46 | // 压缩代码 47 | compressor: { 48 | warnings: false 49 | } 50 | }), 51 | 52 | new webpack.DefinePlugin({ 53 | 'process.env': { 54 | 'NODE_ENV': JSON.stringify('production') 55 | } 56 | }), 57 | // 很多库的内部,有process.NODE_ENV的判断语句, 58 | // 改为production。最直观的就是没有所有的debug相关的东西,体积会减少很多 59 | 60 | 61 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js' ), 62 | // 'vendor' 就是把依赖库(比如react react-router, redux)全部打包到 vendor.js中 63 | // 'vendor.js' 就是把自己写的相关js打包到bundle.js中 64 | // 一般依赖库放到前面,所以vendor放第一个 65 | 66 | new HtmlWebpackPlugin({ 67 | title: '产品模式', 68 | filename:'index.html', 69 | // 文件名以及文件将要存放的位置 70 | 71 | favicon:'./src/favicon.ico', 72 | // favicon路径 73 | 74 | template:'./src/template.html', 75 | // html模板的路径 76 | 77 | inject:'body', 78 | // js插入的位置,true/'head' false/'body' 79 | 80 | chunks: ['vendor', 'index' ], 81 | // 指定引入的chunk,根据entry的key配置,不配置就会引入所有页面的资源 82 | 83 | hash:true, 84 | // 这样每次客户端页面就会根据这个hash来判断页面是否有必要刷新 85 | // 在项目后续过程中,经常需要做些改动更新什么的,一但有改动,客户端页面就会自动更新! 86 | 87 | minify:{ 88 | // 压缩HTML文件 89 | removeComments:true, 90 | // 移除HTML中的注释 91 | 92 | collapseWhitespace:false 93 | // 删除空白符与换行符 94 | } 95 | }) 96 | ], 97 | 98 | resolve: { 99 | extensions: ['', '.js', 'jsx'] 100 | }, 101 | // 实际就是自动添加后缀,默认是当成js文件来查找路径 102 | // 空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式 103 | 104 | module: { 105 | loaders: [ 106 | { 107 | test: /\.js$/, 108 | loaders: ['babel'], 109 | exclude: /node_modules/, 110 | include: __dirname 111 | } 112 | ] 113 | } 114 | } -------------------------------------------------------------------------------- /step-02/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { 5 | "loose": true 6 | } 7 | }], 8 | "react", 9 | "stage-0" 10 | ], 11 | "plugins": [ 12 | "react-hot-loader/babel", 13 | ["transform-runtime", { 14 | "helpers": false, 15 | "polyfill": false, 16 | "regenerator": true, 17 | "moduleName": "babel-runtime" 18 | }] 19 | ] 20 | } -------------------------------------------------------------------------------- /step-02/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-02", 3 | "version": "1.0.0", 4 | "description": "react-base", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npm-run-all --parallel open:src", 8 | "open:src": "babel-node server.js", 9 | "clean": "rimraf dist", 10 | "build:webpack": "webpack --config webpack-pro-config.js --progress --colors", 11 | "build": "npm-run-all clean build:webpack" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "es6", 16 | "webpack" 17 | ], 18 | "author": "minooo", 19 | "license": "MIT", 20 | "dependencies": { 21 | "babel-runtime": "^6.20.0", 22 | "react": "^15.4.1", 23 | "react-dom": "^15.4.1" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^6.4.0", 27 | "babel-cli": "^6.11.4", 28 | "babel-core": "^6.10.4", 29 | "babel-loader": "^6.2.4", 30 | "babel-plugin-transform-runtime": "^6.15.0", 31 | "babel-preset-latest": "^6.16.0", 32 | "babel-preset-react": "^6.11.1", 33 | "babel-preset-stage-0": "^6.5.0", 34 | "browser-sync": "^2.17.5", 35 | "chokidar": "^1.6.1", 36 | "classnames": "^2.2.5", 37 | "connect-history-api-fallback": "^1.3.0", 38 | "cross-env": "^2.0.0", 39 | "css-loader": "^0.23.1", 40 | "express": "^4.14.0", 41 | "file-loader": "^0.9.0", 42 | "html-webpack-plugin": "^2.22.0", 43 | "json-loader": "^0.5.4", 44 | "npm-run-all": "^2.3.0", 45 | "postcss-loader": "^0.9.1", 46 | "postcss-scss": "^0.1.9", 47 | "precss": "^1.4.0", 48 | "react-addons-css-transition-group": "^15.3.0", 49 | "react-hot-loader": "^3.0.0-beta.6", 50 | "redbox-react": "^1.2.10", 51 | "rimraf": "^2.5.3", 52 | "rucksack-css": "^0.8.6", 53 | "style-loader": "^0.13.1", 54 | "url-loader": "^0.5.7", 55 | "webpack": "^1.13.1", 56 | "webpack-dev-middleware": "^1.6.1", 57 | "webpack-hot-middleware": "^2.12.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /step-02/server.js: -------------------------------------------------------------------------------- 1 | // This file configures the development web server 2 | // which supports hot reloading and synchronized testing. 3 | 4 | // Require Browsersync along with webpack and middleware for it 5 | import browserSync from 'browser-sync'; 6 | // Required for react-router browserHistory 7 | // see https://github.com/BrowserSync/browser-sync/issues/204#issuecomment-102623643 8 | import historyApiFallback from 'connect-history-api-fallback'; 9 | import webpack from 'webpack'; 10 | import webpackDevMiddleware from 'webpack-dev-middleware'; 11 | import webpackHotMiddleware from 'webpack-hot-middleware'; 12 | import config from './webpack-dev-config'; 13 | 14 | const bundler = webpack(config); 15 | 16 | // Run Browsersync and use middleware for Hot Module Replacement 17 | browserSync({ 18 | port: 8888, 19 | ui: { 20 | port: 8889 21 | }, 22 | server: { 23 | baseDir: 'src', 24 | 25 | middleware: [ 26 | historyApiFallback(), 27 | 28 | webpackDevMiddleware(bundler, { 29 | // Dev middleware can't access config, so we provide publicPath 30 | publicPath: config.output.publicPath, 31 | 32 | // These settings suppress noisy webpack output so only errors are displayed to the console. 33 | noInfo: false, 34 | quiet: false, 35 | stats: { 36 | assets: false, 37 | colors: true, 38 | version: false, 39 | hash: false, 40 | timings: false, 41 | chunks: false, 42 | chunkModules: false 43 | }, 44 | 45 | // for other settings see 46 | // http://webpack.github.io/docs/webpack-dev-middleware.html 47 | }), 48 | 49 | // bundler should be the same as above 50 | webpackHotMiddleware(bundler) 51 | ] 52 | }, 53 | 54 | // no need to watch '*.js' here, webpack will take care of it for us, 55 | // including full page reloads if HMR won't work 56 | files: [ 57 | 'src/*.html' 58 | ] 59 | }); 60 | -------------------------------------------------------------------------------- /step-02/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/favicon.ico -------------------------------------------------------------------------------- /step-02/src/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/fonts/icomoon.eot -------------------------------------------------------------------------------- /step-02/src/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/fonts/icomoon.ttf -------------------------------------------------------------------------------- /step-02/src/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/fonts/icomoon.woff -------------------------------------------------------------------------------- /step-02/src/images/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/images/big.png -------------------------------------------------------------------------------- /step-02/src/images/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-02/src/images/small.png -------------------------------------------------------------------------------- /step-02/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /step-02/src/index.js: -------------------------------------------------------------------------------- 1 | import './styles/normalize.scss' 2 | import './styles/app.scss' 3 | import './styles/font.scss' 4 | import './styles/animations.scss' 5 | 6 | import {AppContainer} from 'react-hot-loader' 7 | import React from 'react' 8 | import {render} from 'react-dom' 9 | import App from './js/containers/App' 10 | import Redbox from 'redbox-react' 11 | const rootEl = document.getElementById('app'); 12 | 13 | render( 14 | 15 | 16 | , 17 | rootEl 18 | ) 19 | 20 | 21 | if (module.hot) { 22 | module.hot.accept('./js/containers/App', () => { 23 | // If you use Webpack 2 in ES modules mode, you can 24 | // use here rather than require() a . 25 | const NextApp = require('./js/containers/App').default; 26 | render( 27 | 28 | 29 | , 30 | rootEl 31 | ) 32 | }); 33 | } -------------------------------------------------------------------------------- /step-02/src/js/components/Animation/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group' 3 | import styles from './index.scss' 4 | 5 | class Animation extends Component { 6 | state = { 7 | items: [ 8 | { 9 | id: 1, 10 | name: 'react' 11 | }, 12 | { 13 | id: 2, 14 | name: 'react-router' 15 | }, 16 | { 17 | id: 3, 18 | name: 'redux' 19 | }, 20 | { 21 | id: 4, 22 | name: 'es2016' 23 | } 24 | ] 25 | }; 26 | 27 | addItem = () => { 28 | let saySomething = prompt('随便填点什么吧'); 29 | if(saySomething) { 30 | var newItems = this.state.items.concat([ 31 | { 32 | name: saySomething, 33 | id: this.state.items.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1 34 | }]); 35 | this.setState({items: newItems}); 36 | } else {alert('不能为空!')} 37 | }; 38 | 39 | removeItem = (i) => { 40 | var newItems = this.state.items; 41 | newItems.splice(i, 1); 42 | this.setState({ 43 | items: newItems 44 | }) 45 | }; 46 | 47 | render() { 48 | return ( 49 |
50 | 56 |
57 | 62 | {this.state.items.map((item, index) => 63 |
68 | {item.name} 69 |
70 | )} 71 |
72 |
73 | 74 |
75 | ) 76 | } 77 | } 78 | 79 | export default Animation; 80 | -------------------------------------------------------------------------------- /step-02/src/js/components/Animation/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles/mixins.scss"; 2 | @import "../../../styles/app.scss"; 3 | 4 | .addBtn { 5 | @mixin btn 200px, 10px; 6 | i { 7 | color: $main-color; 8 | } 9 | &:hover { 10 | @mixin btn 200px, 10px, $white, $main-color, 14px, 4px, 1px solid $main-color; 11 | i { 12 | color: $white; 13 | } 14 | } 15 | } 16 | 17 | .item { 18 | @mixin btn 400px, 15px, #fff, #9b9b9b, 14px, 0px, 1px solid #9b9b9b; 19 | margin-bottom: 5px; 20 | } 21 | -------------------------------------------------------------------------------- /step-02/src/js/components/BigComponent/BigOne/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './index.scss' 3 | 4 | const BigOne = () => { 5 | return ( 6 |
7 |

8 | 这是一张大图开发(该图片>10k,图片不转代码) 9 |

10 |
11 |
12 | ) 13 | }; 14 | 15 | export default BigOne 16 | -------------------------------------------------------------------------------- /step-02/src/js/components/BigComponent/BigOne/index.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | color: green; 3 | } 4 | .bigImg { 5 | width: 750px; 6 | height: 420px; 7 | background: url("../../../../images/big.png"); 8 | background-size: 100% 100%; 9 | } -------------------------------------------------------------------------------- /step-02/src/js/components/BigComponent/BigTwo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './index.scss' 3 | 4 | const BigTwo = () => { 5 | return ( 6 |
7 |

这是一张小图(该图片<10k,图片转换成代码,F12可查看)

8 |
9 |
10 | ) 11 | } 12 | 13 | export default BigTwo 14 | -------------------------------------------------------------------------------- /step-02/src/js/components/BigComponent/BigTwo/index.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | color: green; 3 | } 4 | .smallImg { 5 | width: 100px; 6 | height: 100px; 7 | background: url("../../../../images/small.png"); 8 | background-size: 100% 100%; 9 | } 10 | -------------------------------------------------------------------------------- /step-02/src/js/components/BigComponent/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import BigOne from './BigOne' 3 | import BigTwo from './BigTwo' 4 | 5 | const BigComponent = () => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ) 12 | }; 13 | 14 | export default BigComponent; -------------------------------------------------------------------------------- /step-02/src/js/components/CommonTitle/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './index.scss' 3 | 4 | const CommonTitle = ({title}) => { 5 | return ( 6 |
7 |

{title}

8 |
9 |
10 | ) 11 | }; 12 | 13 | export default CommonTitle; -------------------------------------------------------------------------------- /step-02/src/js/components/CommonTitle/index.scss: -------------------------------------------------------------------------------- 1 | .toRed { 2 | color: gray; 3 | } -------------------------------------------------------------------------------- /step-02/src/js/components/CssTest/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | 4 | class CssTest extends Component { 5 | render() { 6 | return ( 7 |
8 | 12 | 15 | 18 | 19 | 20 | a标签按钮带图标 21 | 22 | 23 | a标签按钮 24 | 25 | 26 | a标签按钮测试字符长度试字符长度试字符长度试字符长度试字符长度 27 | 28 |
29 | ) 30 | } 31 | } 32 | 33 | export default CssTest 34 | -------------------------------------------------------------------------------- /step-02/src/js/components/CssTest/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles/mixins.scss"; 2 | @import "../../../styles/app.scss"; 3 | 4 | .btnTest { 5 | span { 6 | line-height: 1; 7 | } 8 | @mixin btn 200px, 10px; 9 | i { 10 | color: $main-color; 11 | } 12 | &:hover { 13 | @mixin btn 200px, 10px, $white, $main-color, 14px, 4px, 1px solid $main-color; 14 | i { 15 | color: $white; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /step-02/src/js/components/Tab/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | 4 | class Tab extends Component { 5 | state = { 6 | focused: 0 7 | }; 8 | 9 | handleClick = (index) => { 10 | this.setState({focused: index}) 11 | }; 12 | 13 | render() { 14 | const { items } = this.props; 15 | const { focused } = this.state; 16 | 17 | return ( 18 |
19 |
    20 | {items.map((item, index) => 21 |
  • 26 | {item} 27 |
  • 28 | )} 29 |
30 |

当前选择是:{items[this.state.focused]}

31 |
32 | ) 33 | } 34 | } 35 | 36 | export default Tab -------------------------------------------------------------------------------- /step-02/src/js/components/Tab/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | list-style:none; 3 | display: inline-block; 4 | } 5 | 6 | .common { 7 | display: inline-block; 8 | padding: 10px 20px; 9 | cursor:pointer; 10 | transition:0.3s; 11 | &:hover { 12 | background-color:#beecea ; 13 | } 14 | } 15 | 16 | .normal { 17 | composes: common; 18 | background-color:#eee; 19 | color:#7B8585; 20 | } 21 | 22 | .focused { 23 | composes: common; 24 | color:#fff; 25 | background-color:#41c7c2; 26 | &:hover { 27 | background-color:#41c7c2; 28 | } 29 | } -------------------------------------------------------------------------------- /step-02/src/js/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as BigComponent } from './BigComponent' 2 | export { default as CommonTitle } from './CommonTitle' 3 | export { default as CssTest } from './CssTest/index' 4 | export { default as Tab } from './Tab' 5 | export { default as Animation } from './Animation' 6 | -------------------------------------------------------------------------------- /step-02/src/js/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | BigComponent, 4 | CommonTitle, 5 | CssTest, 6 | Tab, 7 | Animation 8 | } from '../components' 9 | 10 | export default class App extends Component { 11 | state = { 12 | items: ['webpack','react','babel','npm'] 13 | }; 14 | render() { 15 | const {items} = this.state; 16 | 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /step-02/src/styles/animations.scss: -------------------------------------------------------------------------------- 1 | // ReactTransitionGroup 动画 2 | .example-enter { 3 | opacity: 0; 4 | transform: translate(-250px,0); 5 | transform: translate3d(-250px,0,0); 6 | } 7 | .example-enter.example-enter-active { 8 | opacity: 1; 9 | transform: translate(0,0); 10 | transform: translate3d(0,0,0); 11 | transition-property: transform, opacity; 12 | transition-duration: 500ms; 13 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 14 | } 15 | .example-leave { 16 | opacity: 1; 17 | transform: translate(0,0); 18 | transform: translate3d(0,0,0); 19 | transition-property: transform, opacity; 20 | transition-duration: 300ms; 21 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 22 | } 23 | .example-leave.example-leave-active { 24 | opacity: 0; 25 | transform: translate(250px,0); 26 | transform: translate3d(250px,0,0); 27 | } 28 | -------------------------------------------------------------------------------- /step-02/src/styles/font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: url('../fonts/icomoon.eot?8nraly'); 4 | src: url('../fonts/icomoon.eot?8nraly#iefix') format('embedded-opentype'), 5 | url('../fonts/icomoon.ttf?8nraly') format('truetype'), 6 | url('../fonts/icomoon.woff?8nraly') format('woff'), 7 | url('../fonts/icomoon.svg?8nraly#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="i-"], [class*=" i-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icomoon' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .i-sousuo40:before { 27 | content: "\e904"; 28 | } 29 | .i-duihuan40:before { 30 | content: "\e90d"; 31 | } 32 | .i-wode42un:before { 33 | content: "\e912"; 34 | } 35 | .i-wode42:before { 36 | content: "\e913"; 37 | } 38 | .i-haodian42un:before { 39 | content: "\e916"; 40 | } 41 | .i-haodian42:before { 42 | content: "\e917"; 43 | } 44 | .i-shoucang40un:before { 45 | content: "\e925"; 46 | } 47 | .i-shoucang40:before { 48 | content: "\e926"; 49 | } 50 | .i-weixinzhifu32:before { 51 | content: "\e92c"; 52 | } 53 | .i-youhui42un:before { 54 | content: "\e935"; 55 | } 56 | .i-youhui42:before { 57 | content: "\e936"; 58 | } 59 | .i-zhoubian42un:before { 60 | content: "\e938"; 61 | } 62 | .i-zhoubian42:before { 63 | content: "\e939"; 64 | } 65 | .i-zhuye42un:before { 66 | content: "\e93c"; 67 | } 68 | .i-zhuye42:before { 69 | content: "\e93d"; 70 | } 71 | .i-zuixinyouhui40:before { 72 | content: "\e93e"; 73 | } 74 | .i-spinner:before { 75 | content: "\e900"; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /step-02/src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | @define-mixin btn 3 | $width: 100%, 4 | $padding-tb: 15px, 5 | $color: #333, 6 | $background-color: #fff, 7 | $font-size: 14px, 8 | $border-radius: 4px, 9 | $border: 1px solid #ddd, 10 | { 11 | width: $width; 12 | padding: $padding-tb 10px; 13 | color: $color; 14 | background-color: $background-color; 15 | font-size: $font-size; 16 | border-radius: $border-radius; 17 | border: $border; 18 | cursor: pointer; 19 | outline: none; 20 | text-align: center; 21 | } -------------------------------------------------------------------------------- /step-02/src/webpack-public-path.js: -------------------------------------------------------------------------------- 1 | // 服务器静态资源路径配置 2 | __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/"; -------------------------------------------------------------------------------- /step-02/webpack-dev-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 开发模式下的webpack配置 3 | * 在整个项目开发过程中,几乎99%的时间都是在这个模式下进行的 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | 7 | const path = require('path'); 8 | import webpack from 'webpack'; 9 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 10 | 11 | import precss from 'precss'; 12 | import autoprefixer from 'autoprefixer'; 13 | import rucksackCss from 'rucksack-css'; 14 | export default { 15 | debug: true, 16 | devtool: 'cheap-module-eval-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool 17 | noInfo: true, // set to false to see a list of every file being bundled. 18 | entry: [ 19 | './src/webpack-public-path', // 服务器静态资源路径配置,保证首先载入 20 | 'react-hot-loader/patch', 21 | 'webpack-hot-middleware/client?reload=true', 22 | path.resolve(__dirname, 'src/index.js') 23 | ], 24 | target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test 25 | output: { 26 | path: `${__dirname}/src`, // Note: Physical files are only output by the production build task `npm run build`. 27 | publicPath: '/', // 服务器静态资源路径配置 28 | filename: 'bundle.js' 29 | }, 30 | plugins: [ 31 | new webpack.DefinePlugin({ 32 | 'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom) 33 | __DEV__: true 34 | }), 35 | new webpack.HotModuleReplacementPlugin(), 36 | new webpack.NoErrorsPlugin(), 37 | new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS. 38 | template: 'src/index.html', 39 | title: '开发模式', 40 | favicon:'./src/favicon.ico', 41 | minify: { 42 | removeComments: true, 43 | collapseWhitespace: true 44 | }, 45 | hash:true, 46 | // 这样每次客户端页面就会根据这个hash来判断页面是否有必要刷新 47 | // 在项目后续过程中,经常需要做些改动更新什么的,一但有改动,客户端页面就会自动更新! 48 | inject: 'body' 49 | }) 50 | ], 51 | resolve: { 52 | extensions: ['', '.js', 'jsx'], 53 | 54 | // 路径别名, 懒癌福音 55 | alias:{ 56 | app:path.resolve(__dirname,'src/js'), 57 | // 以前你可能这样引用 import { Nav } from '../../components' 58 | // 现在你可以这样引用 import { Nav } from 'app/components' 59 | 60 | style:path.resolve(__dirname,'src/styles') 61 | // 以前你可能这样引用 import "../../../styles/mixins.scss" 62 | // 现在你可以这样引用 import "style/mixins.scss" 63 | 64 | // 注意:别名只能在.js文件中使用。 65 | } 66 | }, 67 | module: { 68 | loaders: [ 69 | { 70 | test: /\.js$/, 71 | loaders: ['babel'], 72 | exclude: /node_modules/ 73 | }, 74 | { 75 | test: /\.scss$/, 76 | include: path.resolve(__dirname, 'src/js'), 77 | loaders: [ 78 | 'style', 79 | 'css?modules&sourceMap&importLoaders=1&localIdentName=[local]___[hash:base64:5]', 80 | 'postcss?parser=postcss-scss' 81 | ] 82 | }, 83 | // 组件样式,需要私有化,单独配置 84 | 85 | { 86 | test: /\.scss$/, 87 | include: path.resolve(__dirname, 'src/styles'), 88 | loader: 'style!css!postcss?parser=postcss-scss' 89 | }, 90 | // 公有样式,不需要私有化,单独配置 91 | 92 | { 93 | test: /\.(otf|eot|svg|ttf|woff|woff2).*$/, 94 | loader: 'url?limit=10000' 95 | }, 96 | { 97 | test: /\.(gif|jpe?g|png|ico)$/, 98 | loader: 'url?limit=10000' 99 | } 100 | ] 101 | }, 102 | postcss: ()=> [precss,autoprefixer,rucksackCss] 103 | }; 104 | -------------------------------------------------------------------------------- /step-02/webpack-pro-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 产品模式下的webpack配置 3 | * 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | var path = require('path'); 7 | var webpack = require('webpack'); 8 | 9 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | // webpack中生成HTML的插件, 11 | 12 | module.exports = { 13 | entry: { 14 | // 文件入口配置 15 | index: './src/index', 16 | vendor: [ 17 | 'react', 18 | 'react-dom' 19 | ] 20 | // 为了优化,切割代码,提取第三方库(实际上,我们将会引入很多第三方库) 21 | }, 22 | // 页面入口文件配置 23 | 24 | output: { 25 | // 文件输出配置 26 | 27 | path: path.join(__dirname, 'dist'), 28 | // 输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它. 29 | 30 | publicPath: '', 31 | // 模板、样式、脚本、图片等资源对应的server上的路径 32 | 33 | filename: 'bundle.js' 34 | // 命名生成的JS 35 | }, 36 | 37 | plugins: [ 38 | new webpack.optimize.OccurrenceOrderPlugin(), 39 | // webapck 会给编译好的代码片段一个id用来区分 40 | // 而这个插件会让webpack在id分配上优化并保持一致性。 41 | // 具体是的优化是:webpack就能够比对id的使用频率和分布来得出最短的id分配给使用频率高的模块 42 | 43 | new webpack.optimize.UglifyJsPlugin({ 44 | // 压缩代码 45 | compressor: { 46 | warnings: false 47 | } 48 | }), 49 | 50 | new webpack.DefinePlugin({ 51 | 'process.env.NODE_ENV': JSON.stringify('production'), 52 | __DEV__: false 53 | }), 54 | // 很多库的内部,有process.NODE_ENV的判断语句, 55 | // 改为production。最直观的就是没有所有的debug相关的东西,体积会减少很多 56 | 57 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js' ), 58 | // 'vendor' 就是把依赖库(比如react react-router, redux)全部打包到 vendor.js中 59 | // 'vendor.js' 就是把自己写的相关js打包到bundle.js中 60 | // 一般依赖库放到前面,所以vendor放第一个 61 | 62 | new HtmlWebpackPlugin({ 63 | template:'src/index.html', 64 | // html模板的路径 65 | 66 | title: '产品模式', 67 | 68 | filename:'index.html', 69 | // 文件名以及文件将要存放的位置 70 | 71 | favicon:'./src/favicon.ico', 72 | // favicon路径 73 | 74 | inject:'body', 75 | // js插入的位置,true/'head' false/'body' 76 | 77 | chunks: ['vendor', 'index' ], 78 | // 指定引入的chunk,根据entry的key配置,不配置就会引入所有页面的资源 79 | 80 | hash:true, 81 | // 这样每次客户端页面就会根据这个hash来判断页面是否有必要刷新 82 | // 在项目后续过程中,经常需要做些改动更新什么的,一但有改动,客户端页面就会自动更新! 83 | 84 | minify:{ 85 | // 压缩HTML文件 86 | removeComments:true, 87 | // 移除HTML中的注释 88 | 89 | collapseWhitespace:true 90 | // 删除空白符与换行符 91 | } 92 | }) 93 | ], 94 | 95 | resolve: { 96 | // 实际就是自动添加后缀,默认是当成js文件来查找路径 97 | // 空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式 98 | extensions: ['', '.js', 'jsx'], 99 | 100 | // 路径别名, 懒癌福音 101 | alias:{ 102 | app:path.resolve(__dirname,'src/js'), 103 | // 以前你可能这样引用 import { Nav } from '../../components' 104 | // 现在你可以这样引用 import { Nav } from 'app/components' 105 | 106 | style:path.resolve(__dirname,'src/styles') 107 | // 以前你可能这样引用 import "../../../styles/mixins.scss" 108 | // 现在你可以这样引用 import "style/mixins.scss" 109 | 110 | // 注意:别名只能在.js文件中使用。 111 | } 112 | }, 113 | 114 | module: { 115 | loaders: [ 116 | { 117 | test: /\.js$/, 118 | loaders: ['babel'], 119 | exclude: /node_modules/ 120 | }, 121 | { 122 | test: /\.scss$/, 123 | include: path.resolve(__dirname, 'src/js'), 124 | loaders: [ 125 | 'style', 126 | 'css?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]', 127 | 'postcss?parser=postcss-scss' 128 | ] 129 | }, 130 | // 组件样式,需要私有化,单独配置 131 | 132 | { 133 | test: /\.scss$/, 134 | include: path.resolve(__dirname, 'src/styles'), 135 | loader: 'style!css!postcss?parser=postcss-scss' 136 | }, 137 | // 公有样式,不需要私有化,单独配置 138 | 139 | { 140 | test: /\.(otf|eot|svg|ttf|woff|woff2).*$/, 141 | loader: 'url?limit=10000' 142 | }, 143 | { 144 | test: /\.(gif|jpe?g|png|ico)$/, 145 | loader: 'url?limit=10000' 146 | } 147 | ] 148 | }, 149 | postcss: function () { 150 | return [ 151 | require('precss'), 152 | require('autoprefixer'), 153 | require('rucksack-css') 154 | ]; 155 | } 156 | }; -------------------------------------------------------------------------------- /step-03/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { 5 | "loose": true 6 | } 7 | }], 8 | "react", 9 | "stage-0" 10 | ], 11 | "plugins": [ 12 | "react-hot-loader/babel", 13 | ["import", { "style": "css", "libraryName": "antd-mobile" }], 14 | ["transform-runtime", { 15 | "helpers": false, 16 | "polyfill": false, 17 | "regenerator": true, 18 | "moduleName": "babel-runtime" 19 | }] 20 | ] 21 | } -------------------------------------------------------------------------------- /step-03/README.md: -------------------------------------------------------------------------------- 1 | # step-03 目前已完成,你可以完整运行 2 | step-03 是在 step-02 的基础上开发的 3 | step-03 主要围绕添加 react-router 进行配置,以及在react移动端开发中,强烈推荐使用[antd-mobile](http://mobile.ant.design/docs/react/introduce) 4 | 这个特别符合我国国情的react组件库。 5 | 本模板延续组件化的思想,以及样式的模块化(cssModules), 并以真实项目实践写了几个简单的组件,包括底部导航,好店列表,以及下拉菜单等。 6 | 目的就是展示下,在真实项目中,组件化的思想是如何实践的。 7 | 8 | ## [DEMO](https://raw.githubusercontent.com/minooo/test/master/step-03-demo.gif) 9 | 点击上方DEMO预览 10 | 11 | ## 高清方案布局(step-03以及step-04已采用此方案) 12 | ``` 13 | // flex模式 14 | 16 | ``` 17 | 18 | > 这是[ant-design-mobile](https://github.com/ant-design/ant-design-mobile/wiki/antd-mobile-0.8%E9%AB%98%E6%B8%85%E6%96%B9%E6%A1%88%E5%AE%9E%E8%B7%B5)的高清方案布局代码,所谓高清方案就是根据设备屏幕的`DPR`(设备像素比,又称`DPPX`,比如dpr=2时,表示1个CSS像素由4个物理像素点组成) 19 | 动态设置 html 的`font-size`, 同时根据设备`DPR`调整页面的缩放值,进而达到高清效果。 20 | 21 | ### 有何优势? 22 | 1. 引用简单,布局简便 23 | 1. 根据设备屏幕的`DPR`,自动设置最合适的高清缩放。 24 | 1. 保证了不同设备下视觉体验的一致性。 25 | 1. 有效解决移动端真实1px问题 26 | 27 | ### 如何使用? 28 | 1. 只需要把上面的js代码放到head里面就行了。 29 | 2. 将设计师给你的效果图(效果图的宽度一般情况下只有三种,640px, 750px, 1442px, 30 | 如果你的效果图属于前两种,那就直接按照效果图上标注的尺寸来布局, 31 | 如果属于第三种,你可能需要把你的效果图宽度等比例缩放至812之后再按照上面标注的尺寸来布局 32 | 第三种之所以这样,是因为此宽度是按照 Iphone 6 Plus 的尺寸给的,此设备的css宽度为414,dpr为3, 33 | 所以它的物理像素宽度为 414 * 3 = 1242。而我们的这个高清布局代码默认 1rem=100px,设备对应的 dpr=2, 34 | 这也就是为什么宽度为640,750的效果图为什么可以直接在上面量取尺寸的原因, 35 | 就是因为640是按照 Iphone 5 的尺寸来的(设备的css宽度为320,dpr=2, 320 * 2 = 640) 36 | 而750是按照 Iphone 6 的尺寸来的(设备的css 宽度为 375,dpr=2, 375 * 2 = 750) 37 | ) 38 | 然后量取效果图上的元素尺寸,用rem做单位进行布局 39 | 比如你量取某个元素宽35px,样式写为`width: 0.35rem`。 40 | 41 | 对阿里高清方案如果不是很理解的朋友,[可以看看我这篇文章](http://www.jianshu.com/p/985d26b40199#comment-7294037) 42 | 3. 对于图片的尺寸,如果是全屏图片,请保证其样式拥有以下设置 43 | ```CSS 44 | .yourImg { 45 | display: block; 46 | width: 100% 47 | } 48 | ``` 49 | 4. 本人心得,关于图片,比如640px宽度的效果图下,你量取某图片宽120px, 高80px, 它的样式应该是`width: 1.2rem; height: 0.8rem` 没问题,但是图片实际尺寸应该是此基础上的1.5倍,即图片应该宽180px, 高120px,这是因为现在很多设备的屏幕的`DPR`达到了3的水平。如此,图片在次屏幕上会“高清显示”。 50 | 51 | ## 本模板包含step-02用到的所有包,下面将介绍 **额外** 添加的包 52 | 53 | #### [react-router](https://github.com/reactjs/react-router) [必需] 54 | > `React Router` 一个针对 `React` 而设计的路由解决方案、可以友好的帮你解决 `React components` 到 `URl` 之间的同步映射关系。 55 | 推荐教程 [React Router 使用教程](http://www.ruanyifeng.com/blog/2016/05/react_router.html) 56 | 安装:`npm install react-router --save` 57 | 58 | --- 59 | 60 | #### [antd-mobile](http://mobile.ant.design/) [强烈推荐] 61 | > antd的移动端版本 62 | 安装:`npm install antd-mobile --save` 63 | 64 | --- 65 | 66 | #### [rc-form](http://mobile.ant.design/) [搭配antd-mobile必需] 67 | > antd-mobile 表单组件需要 68 | 安装:`npm install rc-form --save-dev` 69 | 70 | --- 71 | 72 | #### [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) [搭配antd-mobile必需] 73 | > Modular antd build plugin for babel,让antd-mobile 中模块组件的引入实现**按需加载** 74 | 安装:`npm install babel-plugin-import --save-dev` 75 | 76 | --- 77 | 78 | #### [postcss-pxtorem](https://npm.taobao.org/package/postcss-pxtorem) [antd-mobile高清方案需要] 79 | > 顾名思义,就是将px转化为rem的小插件,实际生产中,我们完全可以直接写rem,这个小插件就是为antd-mobile 的样式服务的。 80 | 使用方法参照[高清方案](https://github.com/ant-design/ant-design-mobile/wiki/antd-mobile-0.8%E9%AB%98%E6%B8%85%E6%96%B9%E6%A1%88%E5%AE%9E%E8%B7%B5),另外特别指出,在此基础上还需在webpack.config.js上配置 81 | ``` 82 | { 83 | test: /\.css$/, 84 | include: /node_modules/, 85 | loader: 'style!css!postcss' 86 | } 87 | ``` 88 | 安装:`npm install babel-plugin-antd --save-dev` 89 | 90 | --- 91 | -------------------------------------------------------------------------------- /step-03/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-03", 3 | "version": "1.0.0", 4 | "description": "react-base", 5 | "main": "index.js", 6 | "theme": "", 7 | "scripts": { 8 | "start": "npm-run-all --parallel open:src", 9 | "open:src": "babel-node server.js", 10 | "clean": "rimraf dist", 11 | "build:webpack": "webpack --config webpack-pro-config.js --progress --colors", 12 | "build": "npm-run-all clean build:webpack" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "es6", 17 | "webpack" 18 | ], 19 | "author": "minooo", 20 | "license": "MIT", 21 | "dependencies": { 22 | "antd-mobile": "^0.9.12", 23 | "babel-runtime": "^6.20.0", 24 | "react": "^15.4.1", 25 | "react-dom": "^15.4.1", 26 | "react-router": "^3.0.0" 27 | }, 28 | "devDependencies": { 29 | "autoprefixer": "^6.4.0", 30 | "babel-cli": "^6.11.4", 31 | "babel-core": "^6.10.4", 32 | "babel-loader": "^6.2.4", 33 | "babel-plugin-import": "^1.0.1", 34 | "babel-plugin-transform-runtime": "^6.15.0", 35 | "babel-preset-latest": "^6.16.0", 36 | "babel-preset-react": "^6.11.1", 37 | "babel-preset-stage-0": "^6.5.0", 38 | "browser-sync": "^2.14.0", 39 | "chokidar": "^1.6.1", 40 | "classnames": "^2.2.5", 41 | "connect-history-api-fallback": "^1.3.0", 42 | "cross-env": "^2.0.0", 43 | "css-loader": "^0.23.1", 44 | "express": "^4.14.0", 45 | "file-loader": "^0.9.0", 46 | "html-webpack-plugin": "^2.22.0", 47 | "json-loader": "^0.5.4", 48 | "npm-run-all": "^2.3.0", 49 | "postcss-loader": "^0.9.1", 50 | "postcss-pxtorem": "^3.3.1", 51 | "postcss-scss": "^0.1.9", 52 | "precss": "^1.4.0", 53 | "rc-form": "^0.17.2", 54 | "react-addons-css-transition-group": "^15.3.0", 55 | "react-hot-loader": "^3.0.0-beta.6", 56 | "redbox-react": "^1.2.10", 57 | "rimraf": "^2.5.3", 58 | "rucksack-css": "^0.8.6", 59 | "style-loader": "^0.13.1", 60 | "url-loader": "^0.5.7", 61 | "webpack": "^1.13.1", 62 | "webpack-dev-middleware": "^1.6.1", 63 | "webpack-hot-middleware": "^2.12.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /step-03/server.js: -------------------------------------------------------------------------------- 1 | // This file configures the development web server 2 | // which supports hot reloading and synchronized testing. 3 | 4 | // Require Browsersync along with webpack and middleware for it 5 | import browserSync from 'browser-sync'; 6 | // Required for react-router browserHistory 7 | // see https://github.com/BrowserSync/browser-sync/issues/204#issuecomment-102623643 8 | import historyApiFallback from 'connect-history-api-fallback'; 9 | import webpack from 'webpack'; 10 | import webpackDevMiddleware from 'webpack-dev-middleware'; 11 | import webpackHotMiddleware from 'webpack-hot-middleware'; 12 | import config from './webpack-dev-config'; 13 | 14 | const bundler = webpack(config); 15 | 16 | // Run Browsersync and use middleware for Hot Module Replacement 17 | browserSync({ 18 | port: 8888, 19 | ui: { 20 | port: 8889 21 | }, 22 | server: { 23 | baseDir: 'src', 24 | 25 | middleware: [ 26 | historyApiFallback(), 27 | 28 | webpackDevMiddleware(bundler, { 29 | // Dev middleware can't access config, so we provide publicPath 30 | publicPath: config.output.publicPath, 31 | 32 | // These settings suppress noisy webpack output so only errors are displayed to the console. 33 | noInfo: false, 34 | quiet: false, 35 | stats: { 36 | assets: false, 37 | colors: true, 38 | version: false, 39 | hash: false, 40 | timings: false, 41 | chunks: false, 42 | chunkModules: false 43 | }, 44 | 45 | // for other settings see 46 | // http://webpack.github.io/docs/webpack-dev-middleware.html 47 | }), 48 | 49 | // bundler should be the same as above 50 | webpackHotMiddleware(bundler) 51 | ] 52 | }, 53 | 54 | // no need to watch '*.js' here, webpack will take care of it for us, 55 | // including full page reloads if HMR won't work 56 | files: [ 57 | 'src/*.html' 58 | ] 59 | }); 60 | -------------------------------------------------------------------------------- /step-03/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/favicon.ico -------------------------------------------------------------------------------- /step-03/src/fonts/dudu.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/fonts/dudu.eot -------------------------------------------------------------------------------- /step-03/src/fonts/dudu.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/fonts/dudu.ttf -------------------------------------------------------------------------------- /step-03/src/fonts/dudu.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/fonts/dudu.woff -------------------------------------------------------------------------------- /step-03/src/images/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/images/big.png -------------------------------------------------------------------------------- /step-03/src/images/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-03/src/images/small.png -------------------------------------------------------------------------------- /step-03/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | <%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /step-03/src/js/components/FastNav/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './index.scss' 4 | 5 | export default class FastNav extends React.Component { 6 | render() { 7 | const { data } = this.props 8 | if (data.link.indexOf('http') > -1) { 9 | return ( 10 | 14 | 19 | 20 | {data.title} 21 | 22 | 23 | ) 24 | } else { 25 | return ( 26 | 30 | 35 | 36 | {data.title} 37 | 38 | 39 | ) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /step-03/src/js/components/FastNav/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 24%; 3 | margin-bottom: 0.3rem; 4 | } 5 | 6 | .thumb { 7 | display: block; 8 | height: 0.94rem; 9 | width: 0.94rem; 10 | border-radius: 0.4rem; 11 | margin-bottom: 0.2rem; 12 | } 13 | 14 | .title { 15 | line-height: 1; 16 | color: #323232; 17 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './index.scss' 4 | 5 | export default class Header extends Component { 6 | state = { 7 | autoCity: '郑州', 8 | searchData: { 9 | cityLink: '/', 10 | cityName: '郑州', 11 | searchLink: '/', 12 | messageLink: '/', 13 | messageBool: true 14 | } 15 | } 16 | 17 | render () { 18 | const { autoCity, searchData } = this.state 19 | 20 | return ( 21 |
22 | 26 | {autoCity} 27 | 28 | 29 | 30 | 34 | 35 | 嗖的一声 36 | 37 | 38 | 42 | {searchData.messageBool && } 43 | 44 | 45 |
46 | ) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /step-03/src/js/components/Header/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | background: #1bbc9b; 3 | height: 0.9rem; 4 | padding: 0 0.3rem; 5 | display: flex; 6 | } 7 | 8 | .left { 9 | color: #fff; 10 | margin-right: 0.4rem; 11 | line-height: 3.5; 12 | &:hover, &:active { 13 | color: #fff; 14 | } 15 | } 16 | 17 | .center { 18 | flex-grow: 1; 19 | margin-top: 0.2rem; 20 | padding-left: 0.2rem; 21 | background: #fff; 22 | height: 0.5rem; 23 | border-radius: 0.25rem; 24 | color: #858585; 25 | &:hover, &:active { 26 | color: #858585; 27 | } 28 | } 29 | 30 | .right { 31 | position: relative; 32 | color: #fff; 33 | font-size: 0.4rem; 34 | line-height: 2.1; 35 | margin-left: 0.3rem; 36 | &:hover, &:active { 37 | color: #fff; 38 | } 39 | } 40 | 41 | .tip { 42 | position: absolute; 43 | display: inline-block; 44 | width: 0.16rem; 45 | height: 0.16rem; 46 | border-radius: 50%; 47 | background: #fe535c; 48 | left: 0.24rem; 49 | top: 0.24rem; 50 | } 51 | 52 | .search { 53 | font-size: 0.3rem; 54 | line-height: 1.7; 55 | margin-right: 0.2rem; 56 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Message/Item/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group' 3 | import styles from './index.scss' 4 | export default class Item extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { focus: 0 }; 8 | this.interval = setInterval(() => this.tick(), 2000); 9 | } 10 | 11 | tick() { 12 | let nowFocus = this.state.focus+1>=this.props.data.length?0:this.state.focus+1 13 | this.setState({ 14 | focus: nowFocus 15 | }); 16 | } 17 | 18 | componentWillUnmount() { 19 | clearInterval(this.interval); 20 | } 21 | 22 | render() { 23 | const { 24 | data 25 | } = this.props 26 | return ( 27 | 32 | {data.map((item, index) => { 33 | if(this.state.focus === index) { 34 | return ( 35 | 40 | {item.desc} 41 | 42 | ) 43 | } 44 | })} 45 | 46 | ) 47 | } 48 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Message/Item/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: block; 3 | height: 0.7rem; 4 | line-height: 0.7rem; 5 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Message/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Item from './Item' 3 | import styles from './index.scss' 4 | 5 | 6 | const Message = ({data}) => { 7 | return ( 8 |
9 |
10 | 11 | 今日消息 12 |
13 |
14 | 15 |
16 |
17 | ) 18 | } 19 | 20 | export default Message -------------------------------------------------------------------------------- /step-03/src/js/components/Message/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | height: 0.7rem; 3 | display: flex; 4 | flex-direction: row; 5 | flex-wrap: nowrap; 6 | overflow: hidden; 7 | border-top: 1px solid #ccc; 8 | } 9 | .left { 10 | padding: 0 0.2rem; 11 | line-height: 1.9; 12 | flex-grow: 0; 13 | flex-shrink: 0; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | border-right: 1px solid #ccc; 18 | } 19 | .right { 20 | flex-grow: 1; 21 | padding-left: 0.2rem; 22 | padding-right: 0.2rem; 23 | } 24 | .ico { 25 | padding-right: 0.1rem; 26 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Nav/NavLink/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './index.scss' 4 | 5 | export default class NavLink extends Component { 6 | render() { 7 | const { pathUrl, active, icoName, linkName, ...rest } = this.props; 8 | return ( 9 | 15 | 16 | {linkName} 17 | 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Nav/NavLink/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | flex: auto; 3 | display: flex; 4 | flex-direction: column; 5 | flex-wrap: wrap; 6 | justify-content: center; 7 | align-items: center; 8 | line-height: 1; 9 | color: #666; 10 | &:hover { 11 | color: #1abc9c; 12 | } 13 | } 14 | 15 | .ico { 16 | font-size: 0.42rem; 17 | display: block; 18 | margin-bottom: 0.1rem; 19 | } 20 | 21 | .text { 22 | display: block; 23 | line-height: 1; 24 | } 25 | -------------------------------------------------------------------------------- /step-03/src/js/components/Nav/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import NavLink from './NavLink' 3 | import styles from './index.scss' 4 | 5 | export default class Nav extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 |
11 | 18 | 24 | 30 | 36 | 42 |
43 |
44 | ) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /step-03/src/js/components/Nav/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | background-color: #f5f5f5; 3 | height: 1rem; 4 | position: fixed; 5 | z-index: 50; 6 | bottom: 0; 7 | left: 0; 8 | width: 100%; 9 | display: flex; 10 | border-top: 1px solid #ccc; 11 | } 12 | 13 | .navOne, .navTwo, .navThree, .navFour, .navFive { 14 | color: #1abc9c; 15 | &:hover, &:active { 16 | color: #1abc9c; 17 | } 18 | } 19 | .navOne i:before { 20 | content: "\e93d"; 21 | } 22 | 23 | .navTwo i:before { 24 | content: "\e917"; 25 | } 26 | 27 | .navThree i:before { 28 | content: "\e936"; 29 | } 30 | 31 | .navFour i:before { 32 | content: "\e939"; 33 | } 34 | 35 | .navFive i:before { 36 | content: "\e913"; 37 | } -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/ConList/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | 4 | class Item extends Component { 5 | onSelectHandle = () => { 6 | this.props.selectHandle(this.props.label, this.props.flag) 7 | } 8 | render() { 9 | const { label, activeBool, SonBool } = this.props 10 | return ( 11 | 16 | {label} 17 | 18 | ) 19 | } 20 | } 21 | 22 | export default class ConList extends Component { 23 | state = { 24 | focus: -1 25 | } 26 | onClickFocus = (label,flag) => { 27 | this.setState({focus: flag}) 28 | } 29 | 30 | render() { 31 | const { items, selectHandle } = this.props 32 | const { focus } = this.state 33 | return ( 34 |
35 |
36 |
37 | {items.map((item, index) => 38 | 46 | )} 47 |
48 | 49 | {items.map((item, index) => 50 | item.items && index===focus && 51 |
52 | {item.items.map((item, index) => 53 | 59 | )} 60 |
61 | )} 62 |
63 |
64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/ConList/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | position: absolute; 3 | top: 0.89rem; 4 | z-index: 150; 5 | width: 100%; 6 | display: flex; 7 | } 8 | 9 | .list { 10 | position: relative; 11 | padding: 0 35px; 12 | height: 0.9rem; 13 | } 14 | 15 | .active { 16 | background: #f2f2f2; 17 | } 18 | 19 | .sonActive { 20 | padding-right: 0.3rem; 21 | border-bottom: 1px solid #ddd; 22 | } 23 | 24 | .left { 25 | flex-grow: 1; 26 | flex-basis: 50%; 27 | background: #fff; 28 | } 29 | 30 | .right { 31 | flex-grow: 1; 32 | flex-basis: 50%; 33 | background: #f2f2f2; 34 | } -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/TopList/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | 4 | /* 5 | export default class TopList extends Component { 6 | onClick = () => { 7 | this.props.onClick(this.props.label) 8 | } 9 | render() { 10 | const { activeBool, label } = this.props 11 | return ( 12 | 17 | {label} 18 | { 19 | activeBool ? 20 | : 21 | 22 | } 23 | 24 | ) 25 | } 26 | }*/ 27 | 28 | /* 29 | * https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md 30 | * 性能优化的两条建议 31 | * 尽量写无状态组件 32 | * 不要在JSX中使用 bind 或者箭头函数 33 | * 如果无状态组件的那个箭头函数无法避免,那与之前的 没有绑定,没有在JSX中使用箭头函数的普通组件相比,两者如何取舍? 34 | * 目前的办法:根据触发的次数来取舍 35 | * 如果触发次数多,那就用普通的组件,反之,就用无状态组件 36 | * */ 37 | 38 | const TopList = (props) => { 39 | return ( 40 | props.onClick(props.label)} 44 | > 45 | {props.label} 46 | { 47 | props.activeBool ? 48 | : 49 | 50 | } 51 | 52 | ) 53 | } 54 | 55 | export default TopList -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/TopList/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | height: 54px; 3 | flex: 1 1 auto; 4 | border-right: 1px solid #ccc; 5 | &:last-child { 6 | border: none; 7 | } 8 | } -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | import TopList from './TopList' 4 | import ConList from './ConList' 5 | 6 | export default class SelectMenu extends Component { 7 | state = { 8 | filters:[ 9 | { 10 | activeBool: false, 11 | key: 'cid', 12 | label: '分类', 13 | items: [ 14 | { 15 | label: '全部分类' 16 | },{ 17 | label: '住宿' 18 | },{ 19 | label: '美食' 20 | },{ 21 | label: '旅游' 22 | } 23 | ] 24 | },{ 25 | activeBool: false, 26 | key: 'bid', 27 | label: '商圈', 28 | items: [ 29 | { 30 | label: '全部分类2' 31 | },{ 32 | label: '住宿2' 33 | },{ 34 | label: '美食2' 35 | },{ 36 | label: '旅游2' 37 | } 38 | ] 39 | },{ 40 | activeBool: false, 41 | key: 'aid', 42 | label: '位置', 43 | items: [ 44 | { 45 | label: '父级项目', 46 | items: [ 47 | { 48 | label: '子项目1' 49 | },{ 50 | label: '子项目2' 51 | },{ 52 | label: '子项目3' 53 | } 54 | ] 55 | },{ 56 | label: '父级项目222', 57 | items: [ 58 | { 59 | label: '子项目11' 60 | },{ 61 | label: '子项目22' 62 | } 63 | ] 64 | },{ 65 | label: '父级项目333', 66 | items: [ 67 | { 68 | label: '子项目111' 69 | },{ 70 | label: '子项目222' 71 | },{ 72 | label: '子项目333' 73 | },{ 74 | label: '子项目444' 75 | },{ 76 | label: '子项目555' 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | ], 83 | isExpend:false 84 | } 85 | handleClick = (key) => { 86 | let filters = this.state.filters 87 | filters.map(item => { 88 | if (key === item.label) { 89 | item.activeBool = !item.activeBool 90 | } else { 91 | item.activeBool = false 92 | } 93 | } 94 | ) 95 | this.setState({ 96 | isExpend: filters.some(item => {return item.activeBool}), 97 | filters: filters 98 | }) 99 | } 100 | hideHandle = () => { 101 | let filters = this.state.filters 102 | filters.map(item => { 103 | item.activeBool = false 104 | }); 105 | this.setState({isExpend : false}); 106 | } 107 | selectHandle = (item) => { 108 | alert(`当前点击的是: ${item}`) 109 | } 110 | render() { 111 | const { filters, isExpend } = this.state 112 | return ( 113 |
114 |
115 | {filters.map(item => 116 | ) 120 | } 121 |
122 | {isExpend && filters.map((cate) => 123 | cate.activeBool && 124 | 125 | )} 126 | 127 | {isExpend &&
} 128 |
129 | ) 130 | } 131 | } -------------------------------------------------------------------------------- /step-03/src/js/components/SelectMenu/index.scss: -------------------------------------------------------------------------------- 1 | .nav { 2 | height: 0.89rem; 3 | } 4 | -------------------------------------------------------------------------------- /step-03/src/js/components/ShopList/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Link} from 'react-router' 3 | import styles from './index.scss' 4 | 5 | const ShopList = ({data}) => { 6 | return ( 7 | 8 |
9 | 10 | {data.label && {data.label}} 11 |
12 | 13 |
14 | {data.discount_scale} 15 | 16 |
17 | 18 |
19 |

20 | {data.title} 21 |

22 | 23 | {data.caption && 24 |

25 | {data.caption} 26 |

} 27 | 28 |

29 | {data.tag}{' '} 30 | | {data.distance} 31 |

32 | 33 | {data.discount_text && 34 |
35 | 36 | {data.discount_text} 37 |
} 38 | 39 | {data.discount_time && 40 |
41 | 42 | 43 | {data.discount_time} 44 | 45 |
} 46 | 47 |
48 | 49 | ) 50 | } 51 | 52 | export default ShopList -------------------------------------------------------------------------------- /step-03/src/js/components/ShopList/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: block; 3 | position: relative; 4 | padding: 0.3rem 0; 5 | border-bottom: 1px solid #ccc; 6 | } 7 | 8 | .rootOneOne { 9 | display: block; 10 | width: 160px; 11 | height: 120px; 12 | } 13 | 14 | .rootOneTwoCommon { 15 | display: block; 16 | position: absolute; 17 | z-index: 10; 18 | left: -0.3rem; 19 | top: 0.14rem; 20 | width: 1.26rem; 21 | height: 0.35rem; 22 | line-height: 0.35rem; 23 | text-align: center; 24 | color: #fff; 25 | font-size: 0.2rem; 26 | transform: rotateZ(315deg) 27 | } 28 | 29 | .rootOneTwo { 30 | composes: rootOneTwoCommon; 31 | background: #19bd9a; 32 | } 33 | 34 | .rootTwo { 35 | margin-left: 1.8rem; 36 | margin-right: 1.5rem; 37 | } 38 | 39 | .rootTwoZero { 40 | font-size: 0.3rem; 41 | line-height: 1; 42 | margin-bottom: 0.09rem; 43 | } 44 | 45 | .rootTwoOne { 46 | line-height: 1; 47 | margin-bottom: 0.09rem; 48 | color: #999; 49 | } 50 | 51 | .rootTwoTwo { 52 | line-height: 1; 53 | margin-bottom: 0.09rem; 54 | } 55 | 56 | .rootTwoTwoOne { 57 | color: #666; 58 | } 59 | 60 | .rootTwoTwoTwo { 61 | color: #999; 62 | } 63 | 64 | .rootTwoThr { 65 | display: flex; 66 | align-items: center; 67 | line-height: 1; 68 | margin-bottom: 0.1rem; 69 | } 70 | 71 | .rootTwoThrOne { 72 | font-size: 0.32rem; 73 | color: #33b433; 74 | margin-right: 0.06rem; 75 | } 76 | 77 | .rootTwoThrTwo { 78 | color: #999; 79 | } 80 | 81 | .rootTwoTFour { 82 | height: 0.32rem; 83 | line-height: 0.32rem; 84 | } 85 | 86 | .rootTwoTFourOne { 87 | font-size: 0.32rem; 88 | color: #848484; 89 | margin-right: 0.06rem; 90 | vertical-align: sub; 91 | } 92 | 93 | .rootTwoTFourTwo { 94 | color: #999; 95 | } 96 | 97 | .rootThr { 98 | position: absolute; 99 | right: 0.1rem; 100 | top: 50%; 101 | transform: translateY(-50%); 102 | } 103 | 104 | .rootThrOne { 105 | font-size: 0.7rem; 106 | color: #ec6957; 107 | } 108 | 109 | .rootThrTwo { 110 | color: #ec6957; 111 | } 112 | 113 | .listDelete { 114 | flex-basis: 0.45rem; 115 | flex-shrink: 0; 116 | display: flex; 117 | align-items: center; 118 | font-size: 0.4rem; 119 | color: red; 120 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Swipe/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Carousel } from 'antd-mobile' 3 | 4 | export default class Swipe extends Component { 5 | render() { 6 | const { data, hackHeight, ...rest } = this.props 7 | /*如果接收到一张图片就单纯展示,如果是多张,就用Carousel组件展示*/ 8 | let list 9 | if (data.length === 1) { 10 | list = 11 | 12 | 16 | 17 | } else { 18 | list = 19 | 22 | { 23 | data.map((item,index) => 24 | 25 | 29 | 30 | ) 31 | } 32 | 33 | } 34 | return ( 35 |
36 | {list} 37 |
38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /step-03/src/js/components/Title/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './index.scss' 4 | 5 | export default class Title extends Component { 6 | componentDidMount() { 7 | window.addEventListener('resize', this.whenResize) 8 | } 9 | whenResize = () => { 10 | console.log('Title组件悄悄告诉你:窗口变化了') 11 | } 12 | render() { 13 | const { text, linkName, path } = this.props; 14 | return ( 15 |

16 | {text} 17 | { 18 | linkName && 19 | 20 | {linkName} 21 | 22 | 23 | } 24 |

25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /step-03/src/js/components/Title/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | height: 0.8rem; 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding-left: 0.15rem; 7 | padding-right: 0.15rem; 8 | } 9 | .left { 10 | color: #999; 11 | font-weight: normal; 12 | } 13 | .link { 14 | color: #19bd9b; 15 | display: flex; 16 | align-items: center; 17 | } 18 | .text { 19 | font-style: normal; 20 | } 21 | .icon { 22 | font-size: 0.3rem; 23 | } -------------------------------------------------------------------------------- /step-03/src/js/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Nav } from './Nav' 2 | export { default as Header } from './Header' 3 | export { default as FastNav } from './FastNav' 4 | export { default as Title } from './Title' 5 | export { default as SelectMenu } from './SelectMenu' 6 | export { default as ShopList } from './ShopList' 7 | export { default as Message } from './Message' 8 | export { default as Swipe } from './Swipe' -------------------------------------------------------------------------------- /step-03/src/js/containers/404.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | 4 | export default class Tour extends Component { 5 | render() { 6 | return ( 7 |
8 | 404!页面没有找到,请 9 | 返回首页 10 |
11 | ) 12 | } 13 | } -------------------------------------------------------------------------------- /step-03/src/js/containers/App.js: -------------------------------------------------------------------------------- 1 | import React,{ Component } from 'react'; 2 | export default class App extends Component { 3 | render() { 4 | const { children } =this.props; 5 | return children 6 | } 7 | } -------------------------------------------------------------------------------- /step-03/src/js/containers/Coupon/Detail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default class Detail extends Component { 4 | render() { 5 | return ( 6 |
7 | Coupon Detail 8 |
9 | ) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /step-03/src/js/containers/Coupon/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import { Nav } from '../../components' 4 | export default class Coupon extends Component { 5 | render() { 6 | return ( 7 |
8 |

Coupon

9 |

10 | Consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 11 |

12 | 详情 13 |
15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /step-03/src/js/containers/Home.js: -------------------------------------------------------------------------------- 1 | import React,{ Component } from 'react' 2 | import { Header, Nav, Swipe, FastNav, Title, ShopList, Message } from '../components' 3 | 4 | class Home extends Component { 5 | state = 6 | { 7 | swipeImgHeight:0, 8 | swipeList: [ 9 | {link: 'https://www.github.com', thumb: 'http://temp.im/640x260/444/fff'}, 10 | {link: 'https://www.github.com', thumb: 'http://temp.im/640x260/444/fff'}, 11 | {link: 'https://www.github.com', thumb: 'http://temp.im/640x260/444/fff'}, 12 | {link: 'https://www.github.com', thumb: 'http://temp.im/640x260/444/fff'}, 13 | ], 14 | fastNav: [ 15 | { 16 | link: "https://www.github.com", 17 | thumb: "http://temp.im/94x94", 18 | title: "大牌抢购" 19 | }, 20 | { 21 | link: "https://www.github.com", 22 | thumb: "http://temp.im/94x94", 23 | title: "大牌抢购" 24 | }, 25 | { 26 | link: "https://www.github.com", 27 | thumb: "http://temp.im/94x94", 28 | title: "大牌抢购" 29 | }, 30 | { 31 | link: "https://www.github.com", 32 | thumb: "http://temp.im/94x94", 33 | title: "大牌抢购" 34 | }, 35 | { 36 | link: "https://www.github.com", 37 | thumb: "http://temp.im/94x94", 38 | title: "大牌抢购" 39 | }, 40 | { 41 | link: "/hello/", 42 | thumb: "http://temp.im/94x94", 43 | title: "大牌抢购" 44 | } 45 | ], 46 | shopLists: [ 47 | { 48 | id: 1, 49 | thumb: 'http://temp.im/160x120/FF9500/000', 50 | label: '五折优惠', 51 | discount_scale: '8', 52 | title: '年年有鱼', 53 | caption: '本市最棒的烤鱼店', 54 | tag: '美食', 55 | distance: '2.5km', 56 | discount_text: '本店支持微信支付', 57 | discount_time: '周一到周五每天9:00-24:00' 58 | },{ 59 | id: 2, 60 | thumb: 'http://temp.im/160x120/FF9500/000', 61 | label: '五折优惠', 62 | discount_scale: '9', 63 | title: '年年有鱼2', 64 | caption: '本市最棒的烤鱼店2', 65 | tag: '美食', 66 | distance: '12.5km', 67 | discount_text: '本店支持微信支付', 68 | discount_time: '周一到周五每天9:00-24:00' 69 | } 70 | ], 71 | messages: [ 72 | { 73 | link: 'https://github.com/minooo', 74 | desc: 'React-Study step-1 开发完成' 75 | },{ 76 | link: 'https://github.com/minooo', 77 | desc: 'React-Study step-2 开发完成' 78 | },{ 79 | link: 'https://github.com/minooo', 80 | desc: 'React-Study step-3 开发完成, 如果很长很长如果很长很长如果很长很长如果很长很长' 81 | } 82 | ] 83 | } 84 | 85 | componentDidMount() { 86 | 87 | } 88 | 89 | render() { 90 | const settings = { 91 | autoplay: true, 92 | autoplayInterval: 3000, 93 | infinite: true, 94 | dots: true 95 | } 96 | 97 | const { swipeList, fastNav, shopLists, messages } = this.state 98 | 99 | return ( 100 |
101 |
102 | { 103 | swipeList && 104 | 105 | } 106 |
107 | {fastNav.map((item, index) => 108 | 109 | )} 110 |
111 | 112 | 113 | <div className="plr20 bg-white"> 114 | {shopLists.map(item => 115 | <ShopList key={item.id} data={item} /> 116 | )} 117 | </div> 118 | <Nav /> 119 | </div> 120 | ) 121 | } 122 | } 123 | 124 | export default Home -------------------------------------------------------------------------------- /step-03/src/js/containers/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import routes from '../routes' 3 | import { Router } from 'react-router' 4 | import '../../styles/normalize.scss' 5 | import '../../styles/app.scss' 6 | import '../../styles/antdStyleReset.scss' 7 | import '../../styles/font.scss' 8 | import '../../styles/animations.scss' 9 | 10 | export default class Root extends Component { 11 | render() { 12 | const { history } = this.props; 13 | return ( 14 | <Router history={history} routes={routes} /> 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /step-03/src/js/containers/Shop/Detail.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavBar } from 'antd-mobile'; 3 | export default class Detail extends Component { 4 | render() { 5 | const settings = { 6 | dots: true, 7 | autoplay: true, 8 | infinite: true, 9 | mode: 'banner' 10 | }; 11 | return ( 12 | <div className="box"> 13 | <NavBar mode="light" onLeftClick={() => {history.go(-1)}}>好店详情</NavBar> 14 | <div className="font-34">空落落的什么也没有 - -!</div> 15 | </div> 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /step-03/src/js/containers/Shop/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Nav, Header, SelectMenu } from '../../components' 3 | 4 | export default class Shop extends Component { 5 | render() { 6 | return ( 7 | <div className="box"> 8 | <Header /> 9 | <SelectMenu/> 10 | <Nav /> 11 | </div> 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /step-03/src/js/containers/Tour/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Nav } from '../../components' 3 | export default class Tour extends Component { 4 | render() { 5 | return ( 6 | <div className="box"> 7 | Tour 8 | <Nav /> 9 | </div> 10 | ) 11 | } 12 | } -------------------------------------------------------------------------------- /step-03/src/js/containers/User/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Nav } from '../../components' 3 | export default class User extends Component { 4 | render() { 5 | return ( 6 | <div className="box"> 7 | User 8 | <Nav /> 9 | </div> 10 | ) 11 | } 12 | } -------------------------------------------------------------------------------- /step-03/src/js/containers/index.js: -------------------------------------------------------------------------------- 1 | export {default as App} from './App' 2 | 3 | // 首页 4 | export { default as Home } from './Home' 5 | 6 | // 优惠 7 | export { default as Coupon } from './Coupon' 8 | export { default as CouponDetail } from './Coupon/Detail' 9 | 10 | // 好店 11 | export { default as Shop } from './Shop' 12 | export { default as ShopDetail } from './Shop/Detail' 13 | 14 | // 周边 15 | export { default as Tour } from './Tour' 16 | 17 | // 我的 18 | export { default as User } from './User' 19 | 20 | // 404 21 | export { default as NotFoundPage } from './404.js' -------------------------------------------------------------------------------- /step-03/src/js/index.js: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { browserHistory } from 'react-router' 5 | import Root from './containers/Root' 6 | import Redbox from 'redbox-react' 7 | const rootEl = document.getElementById('app'); 8 | 9 | render( 10 | <AppContainer errorReporter={Redbox}> 11 | <Root history={browserHistory} /> 12 | </AppContainer>, 13 | rootEl 14 | ) 15 | 16 | if (module.hot) { 17 | /** 18 | * Warning from React Router, caused by react-hot-loader. 19 | * The warning can be safely ignored, so filter it from the console. 20 | * Otherwise you'll see it every time something changes. 21 | * See https://github.com/gaearon/react-hot-loader/issues/298 22 | */ 23 | const orgError = console.error; // eslint-disable-line no-console 24 | console.error = (message) => { // eslint-disable-line no-console 25 | if (message && message.indexOf('You cannot change <Router routes>;') === -1) { 26 | // Log the error as normally 27 | orgError.apply(console, [message]); 28 | } 29 | }; 30 | module.hot.accept('./containers/Root', () => { 31 | // If you use Webpack 2 in ES modules mode, you can 32 | // use <App /> here rather than require() a <NextApp />. 33 | const NextApp = require('./containers/Root').default; 34 | render( 35 | <AppContainer errorReporter={Redbox}> 36 | <NextApp history={browserHistory} /> 37 | </AppContainer>, 38 | rootEl 39 | ) 40 | }); 41 | } -------------------------------------------------------------------------------- /step-03/src/js/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, IndexRoute } from 'react-router' 3 | 4 | import { 5 | App, 6 | Home, 7 | Coupon, 8 | CouponDetail, 9 | Shop, 10 | ShopDetail, 11 | Tour, 12 | User, 13 | NotFoundPage, 14 | } from './containers' 15 | 16 | export default ( 17 | <Route path="/" component={App}> 18 | <IndexRoute component={Home}/> 19 | <Route path="coupon"> 20 | <IndexRoute component={Coupon}/> 21 | <Route path=":id" component={CouponDetail}/> 22 | </Route> 23 | <Route path="shop"> 24 | <IndexRoute component={Shop}/> 25 | <Route path=":id" component={ShopDetail}/> 26 | </Route> 27 | <Route path="tour" component={Tour}/> 28 | <Route path="user" component={User}/> 29 | <Route path="*" component={NotFoundPage}/> 30 | </Route> 31 | ); -------------------------------------------------------------------------------- /step-03/src/styles/animations.scss: -------------------------------------------------------------------------------- 1 | // ReactTransitionGroup 动画 2 | .example-enter { 3 | opacity: 0; 4 | transform: translate(-250px,0); 5 | transform: translate3d(-250px,0,0); 6 | } 7 | .example-enter.example-enter-active { 8 | opacity: 1; 9 | transform: translate(0,0); 10 | transform: translate3d(0,0,0); 11 | transition-property: transform, opacity; 12 | transition-duration: 500ms; 13 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 14 | } 15 | .example-leave { 16 | opacity: 1; 17 | transform: translate(0,0); 18 | transform: translate3d(0,0,0); 19 | transition-property: transform, opacity; 20 | transition-duration: 300ms; 21 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 22 | } 23 | .example-leave.example-leave-active { 24 | opacity: 0; 25 | transform: translate(250px,0); 26 | transform: translate3d(250px,0,0); 27 | } 28 | 29 | // 首页导航过场动画 30 | .swap-enter { 31 | opacity: 0.01; 32 | transform: translateX(5em); 33 | transition: all .5s ease-out; 34 | } 35 | 36 | .swap-enter.swap-enter-active { 37 | opacity: 1; 38 | transform: translateX(0); 39 | } 40 | 41 | .swap-leave { 42 | opacity: 1; 43 | transform: translateX(0); 44 | transition: all .5s ease-in; 45 | } 46 | 47 | .swap-leave.swap-leave-active { 48 | opacity: 0; 49 | transform: translateX(-5em); 50 | } 51 | 52 | // message 组件动画 53 | .message-enter { 54 | opacity: 0; 55 | transform: translate(0,-25px); 56 | transform: translate3d(0,-25px,0); 57 | } 58 | .message-enter.message-enter-active { 59 | opacity: 1; 60 | transform: translate(0,0); 61 | transform: translate3d(0,0,0); 62 | transition-property: transform, opacity; 63 | transition-duration: 500ms; 64 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 65 | } 66 | .message-leave { 67 | opacity: 1; 68 | transform: translate(0,0); 69 | transform: translate3d(0,0,0); 70 | transition-property: transform, opacity; 71 | transition-duration: 300ms; 72 | transition-timing-function: cubic-bezier(0.175, 0.665, 0.320, 1), linear; 73 | } 74 | .message-leave.message-leave-active { 75 | opacity: 0; 76 | transform: translate(0,25px); 77 | transform: translate3d(0,25px,0); 78 | } -------------------------------------------------------------------------------- /step-03/src/styles/antdStyleReset.scss: -------------------------------------------------------------------------------- 1 | //重置antd-mobile样式 2 | .am-carousel .slick-dots li.slick-active button:before { 3 | color: #1bbc9b; 4 | opacity: 1; 5 | } 6 | .am-navbar-light { 7 | color: #1abc9c; 8 | } 9 | // 重写radio样式 10 | .am-radio.am-radio-checked .am-radio-inner:after { 11 | position: absolute; 12 | z-index: 100; 13 | content: ''; 14 | display: block; 15 | width: 10px; 16 | height: 10px; 17 | left: 5px; 18 | top: 5px; 19 | background: #1abc9c; 20 | border-radius: 50%; 21 | border: none; 22 | } 23 | 24 | .am-radio-inner:before { 25 | position: absolute; 26 | content: ''; 27 | display: block; 28 | width: 20px; 29 | height: 20px; 30 | border: 1px solid #999; 31 | background: #fff; 32 | left: 0; 33 | top: 0; 34 | z-index: 100; 35 | border-radius: 50%; 36 | outline: 0; 37 | } 38 | 39 | .am-radio-inner { 40 | right: 3px; 41 | } -------------------------------------------------------------------------------- /step-03/src/styles/font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'dudu'; 3 | src: url('../fonts/dudu.eot?wl8wsb'); 4 | src: url('../fonts/dudu.eot?wl8wsb#iefix') format('embedded-opentype'), 5 | url('../fonts/dudu.ttf?wl8wsb') format('truetype'), 6 | url('../fonts/dudu.woff?wl8wsb') format('woff'), 7 | url('../fonts/dudu.svg?wl8wsb#dudu') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="i-"], [class*=" i-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'dudu' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .i-haodian42un:before { 27 | content: "\e916"; 28 | } 29 | .i-jianpan60:before { 30 | content: "\e901"; 31 | } 32 | .i-fanhui50:before { 33 | content: "\e902"; 34 | } 35 | .i-shezhi40:before { 36 | content: "\e903"; 37 | } 38 | .i-sousuo40:before { 39 | content: "\e904"; 40 | } 41 | .i-bangzhu40:before { 42 | content: "\e905"; 43 | } 44 | .i-bainji40:before { 45 | content: "\e906"; 46 | } 47 | .i-xuanze24un:before { 48 | content: "\e907"; 49 | } 50 | .i-xuanze24:before { 51 | content: "\e908"; 52 | } 53 | .i-qianggou42un:before { 54 | content: "\e909"; 55 | } 56 | .i-qianggou42:before { 57 | content: "\e90a"; 58 | } 59 | .i-daifukuan40:before { 60 | content: "\e90b"; 61 | } 62 | .i-zuobiao32:before { 63 | content: "\e90c"; 64 | } 65 | .i-duihuan40:before { 66 | content: "\e90d"; 67 | } 68 | .i-jilu40:before { 69 | content: "\e90e"; 70 | } 71 | .i-erweima32:before { 72 | content: "\e90f"; 73 | } 74 | .i-fanhui40:before { 75 | content: "\e910"; 76 | } 77 | .i-you40:before { 78 | content: "\e911"; 79 | } 80 | .i-wode42un:before { 81 | content: "\e912"; 82 | } 83 | .i-wode42:before { 84 | content: "\e913"; 85 | } 86 | .i-guanyu32:before { 87 | content: "\e914"; 88 | } 89 | .i-tixing40:before { 90 | content: "\e915"; 91 | } 92 | .i-haodian42:before { 93 | content: "\e917"; 94 | } 95 | .i-jifen40:before { 96 | content: "\e918"; 97 | } 98 | .i-409:before { 99 | content: "\e919"; 100 | } 101 | .i-qiandao40:before { 102 | content: "\e91a"; 103 | } 104 | .i-jiudian40:before { 105 | content: "\e91b"; 106 | } 107 | .i-dianhua32:before { 108 | content: "\e91c"; 109 | } 110 | .i-lvyou40:before { 111 | content: "\e91d"; 112 | } 113 | .i-meishi40:before { 114 | content: "\e91e"; 115 | } 116 | .i-qinzibang40:before { 117 | content: "\e91f"; 118 | } 119 | .i-quanbudingdan40:before { 120 | content: "\e920"; 121 | } 122 | .i-shanchu32:before { 123 | content: "\e921"; 124 | } 125 | .i-dianping38:before { 126 | content: "\e922"; 127 | } 128 | .i-4016:before { 129 | content: "\e923"; 130 | } 131 | .i-shijian28:before { 132 | content: "\e924"; 133 | } 134 | .i-shoucang40un:before { 135 | content: "\e925"; 136 | } 137 | .i-shoucang40:before { 138 | content: "\e926"; 139 | } 140 | .i-4019:before { 141 | content: "\e927"; 142 | } 143 | .i-4020:before { 144 | content: "\e928"; 145 | } 146 | .i-xiangji38:before { 147 | content: "\e929"; 148 | } 149 | .i-tongchengju40:before { 150 | content: "\e92a"; 151 | } 152 | .i-tongcheng40:before { 153 | content: "\e92b"; 154 | } 155 | .i-weixinzhifu32:before { 156 | content: "\e92c"; 157 | } 158 | .i-weipingjia40:before { 159 | content: "\e92d"; 160 | } 161 | .i-weixiaofei40:before { 162 | content: "\e92e"; 163 | } 164 | .i-hongbao32:before { 165 | content: "\e92f"; 166 | } 167 | .i-wuzhe40:before { 168 | content: "\e930"; 169 | } 170 | .i-xiuxian40:before { 171 | content: "\e931"; 172 | } 173 | .i-yiriyou40:before { 174 | content: "\e932"; 175 | } 176 | .i-yifkuan40:before { 177 | content: "\e933"; 178 | } 179 | .i-shijianmian28:before { 180 | content: "\e934"; 181 | } 182 | .i-youhui42un:before { 183 | content: "\e935"; 184 | } 185 | .i-youhui42:before { 186 | content: "\e936"; 187 | } 188 | .i-youjiantou24:before { 189 | content: "\e937"; 190 | } 191 | .i-zhoubian42un:before { 192 | content: "\e938"; 193 | } 194 | .i-zhoubian42:before { 195 | content: "\e939"; 196 | } 197 | .i-zhoubianzuobiao28:before { 198 | content: "\e93a"; 199 | } 200 | .i-zhoumoyou40:before { 201 | content: "\e93b"; 202 | } 203 | .i-zhuye42un:before { 204 | content: "\e93c"; 205 | } 206 | .i-zhuye42:before { 207 | content: "\e93d"; 208 | } 209 | .i-zuixinyouhui40:before { 210 | content: "\e93e"; 211 | } 212 | .i-huangguan:before { 213 | content: "\e93f"; 214 | } 215 | .i-spinner:before { 216 | content: "\e900"; 217 | } -------------------------------------------------------------------------------- /step-03/src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | @define-mixin btn 3 | $width: 100%, 4 | $padding-tb: 0.15rem, 5 | $color: #333, 6 | $background-color: #fff, 7 | $font-size: 14px, 8 | $border-radius: 4px, 9 | $border: 1px solid #ddd, 10 | { 11 | width: $width; 12 | padding: $padding-tb 0.1rem; 13 | color: $color; 14 | background-color: $background-color; 15 | font-size: $font-size; 16 | border-radius: $border-radius; 17 | border: $border; 18 | cursor: pointer; 19 | outline: none; 20 | text-align: center; 21 | } -------------------------------------------------------------------------------- /step-03/src/webpack-public-path.js: -------------------------------------------------------------------------------- 1 | // 服务器静态资源路径配置 2 | __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/"; -------------------------------------------------------------------------------- /step-03/webpack-dev-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 开发模式下的webpack配置 3 | * 在整个项目开发过程中,几乎99%的时间都是在这个模式下进行的 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | 7 | const path = require('path'); 8 | import webpack from 'webpack'; 9 | import HtmlWebpackPlugin from 'html-webpack-plugin' 10 | 11 | import precss from 'precss' 12 | import autoprefixer from 'autoprefixer' 13 | import rucksackCss from 'rucksack-css' 14 | import px2rem from 'postcss-pxtorem'; 15 | const px2remOpts = { 16 | rootValue: 100, 17 | propWhiteList: [], 18 | }; 19 | 20 | export default { 21 | debug: true, 22 | devtool: 'cheap-module-eval-source-map', // more info:https://webpack.github.io/docs/build-performance.html#sourcemaps and https://webpack.github.io/docs/configuration.html#devtool 23 | noInfo: true, // set to false to see a list of every file being bundled. 24 | entry: [ 25 | './src/webpack-public-path', // 服务器静态资源路径配置,保证首先载入 26 | 'react-hot-loader/patch', 27 | 'webpack-hot-middleware/client?reload=true', 28 | path.resolve(__dirname, 'src/js/index.js') 29 | ], 30 | target: 'web', // necessary per https://webpack.github.io/docs/testing.html#compile-and-test 31 | output: { 32 | path: `${__dirname}/src`, // Note: Physical files are only output by the production build task `npm run build`. 33 | publicPath: '/', 34 | filename: 'bundle.js' 35 | }, 36 | plugins: [ 37 | new webpack.DefinePlugin({ 38 | 'process.env.NODE_ENV': JSON.stringify('development'), // Tells React to build in either dev or prod modes. https://facebook.github.io/react/downloads.html (See bottom) 39 | __DEV__: true 40 | }), 41 | new webpack.HotModuleReplacementPlugin(), 42 | new webpack.NoErrorsPlugin(), 43 | new HtmlWebpackPlugin({ // Create HTML file that includes references to bundled CSS and JS. 44 | template: 'src/index.html', 45 | title: '开发模式', 46 | favicon:'./src/favicon.ico', 47 | minify: { 48 | removeComments: true, 49 | collapseWhitespace: true 50 | }, 51 | hash:true, 52 | // 这样每次客户端页面就会根据这个hash来判断页面是否有必要刷新 53 | // 在项目后续过程中,经常需要做些改动更新什么的,一但有改动,客户端页面就会自动更新! 54 | inject: 'body' 55 | }) 56 | ], 57 | resolve: { 58 | modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')], 59 | extensions: ['', '.web.js', '.js', '.json'], 60 | 61 | // 路径别名, 懒癌福音 62 | alias:{ 63 | app:path.resolve(__dirname,'src/js'), 64 | // 以前你可能这样引用 import { Nav } from '../../components' 65 | // 现在你可以这样引用 import { Nav } from 'app/components' 66 | 67 | style:path.resolve(__dirname,'src/styles') 68 | // 以前你可能这样引用 import "../../../styles/mixins.scss" 69 | // 现在你可以这样引用 import "style/mixins.scss" 70 | 71 | // 注意:别名只能在.js文件中使用。 72 | } 73 | }, 74 | module: { 75 | loaders: [ 76 | { 77 | test: /\.js$/, 78 | loaders: ['babel'], 79 | exclude: /node_modules/ 80 | }, 81 | { 82 | test: /\.scss$/, 83 | include: path.resolve(__dirname, 'src/js'), 84 | loaders: [ 85 | 'style', 86 | 'css?modules&sourceMap&importLoaders=1&localIdentName=[local]___[hash:base64:5]', 87 | 'postcss?parser=postcss-scss' 88 | ] 89 | }, 90 | // 组件样式,需要私有化,单独配置 91 | 92 | { 93 | test: /\.scss$/, 94 | include: path.resolve(__dirname, 'src/styles'), 95 | loader: 'style!css!postcss?parser=postcss-scss' 96 | }, 97 | // 公有样式,不需要私有化,单独配置 98 | 99 | { 100 | test: /\.css$/, 101 | include: path.resolve(__dirname, 'node_modules'), 102 | loader: 'style!css!postcss' 103 | }, 104 | 105 | { 106 | test: /\.(otf|eot|svg|ttf|woff|woff2).*$/, 107 | loader: 'url?limit=10000' 108 | }, 109 | { 110 | test: /\.(gif|jpe?g|png|ico)$/, 111 | loader: 'url?limit=10000' 112 | } 113 | ] 114 | }, 115 | postcss: ()=> [precss,autoprefixer,rucksackCss,px2rem(px2remOpts)] 116 | }; 117 | -------------------------------------------------------------------------------- /step-03/webpack-pro-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 产品模式下的webpack配置 3 | * 4 | * 注意。两种模式的配置有较大差异!! 5 | */ 6 | var path = require('path'); 7 | var webpack = require('webpack'); 8 | 9 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | const pxtorem = require('postcss-pxtorem'); 11 | // webpack中生成HTML的插件, 12 | 13 | module.exports = { 14 | entry: { 15 | // 文件入口配置 16 | index: './src/js/index', 17 | vendor: [ 18 | 'react', 19 | 'react-dom', 20 | 'react-router' 21 | ] 22 | // 为了优化,切割代码,提取第三方库(实际上,我们将会引入很多第三方库) 23 | }, 24 | // 页面入口文件配置 25 | 26 | output: { 27 | // 文件输出配置 28 | 29 | path: path.join(__dirname, 'dist'), 30 | // 输出目录的配置,模板、样式、脚本、图片等资源的路径配置都相对于它. 31 | 32 | publicPath: '', 33 | // 模板、样式、脚本、图片等资源对应的server上的路径 34 | 35 | filename: 'bundle.js' 36 | // 命名生成的JS 37 | }, 38 | 39 | plugins: [ 40 | new webpack.optimize.OccurrenceOrderPlugin(), 41 | // webapck 会给编译好的代码片段一个id用来区分 42 | // 而这个插件会让webpack在id分配上优化并保持一致性。 43 | // 具体是的优化是:webpack就能够比对id的使用频率和分布来得出最短的id分配给使用频率高的模块 44 | 45 | new webpack.optimize.UglifyJsPlugin({ 46 | // 压缩代码 47 | compressor: { 48 | warnings: false 49 | } 50 | }), 51 | 52 | new webpack.DefinePlugin({ 53 | 'process.env.NODE_ENV': JSON.stringify('production'), 54 | __DEV__: false 55 | }), 56 | // 很多库的内部,有process.NODE_ENV的判断语句, 57 | // 改为production。最直观的就是没有所有的debug相关的东西,体积会减少很多 58 | 59 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js' ), 60 | // 'vendor' 就是把依赖库(比如react react-router, redux)全部打包到 vendor.js中 61 | // 'vendor.js' 就是把自己写的相关js打包到bundle.js中 62 | // 一般依赖库放到前面,所以vendor放第一个 63 | 64 | new HtmlWebpackPlugin({ 65 | template:'src/index.html', 66 | // html模板的路径 67 | 68 | title: '产品模式', 69 | 70 | filename:'index.html', 71 | // 文件名以及文件将要存放的位置 72 | 73 | favicon:'./src/favicon.ico', 74 | // favicon路径 75 | 76 | inject:'body', 77 | // js插入的位置,true/'head' false/'body' 78 | 79 | chunks: ['vendor', 'index' ], 80 | // 指定引入的chunk,根据entry的key配置,不配置就会引入所有页面的资源 81 | 82 | hash:true, 83 | // 这样每次客户端页面就会根据这个hash来判断页面是否有必要刷新 84 | // 在项目后续过程中,经常需要做些改动更新什么的,一但有改动,客户端页面就会自动更新! 85 | 86 | minify:{ 87 | // 压缩HTML文件 88 | removeComments:true, 89 | // 移除HTML中的注释 90 | 91 | collapseWhitespace:true 92 | // 删除空白符与换行符 93 | } 94 | }) 95 | ], 96 | 97 | resolve: { 98 | // 实际就是自动添加后缀,默认是当成js文件来查找路径 99 | // 空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式 100 | modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')], 101 | extensions: ['', '.web.js', '.js', '.json'], 102 | 103 | // 路径别名, 懒癌福音 104 | alias:{ 105 | app:path.resolve(__dirname,'src/js'), 106 | // 以前你可能这样引用 import { Nav } from '../../components' 107 | // 现在你可以这样引用 import { Nav } from 'app/components' 108 | 109 | style:path.resolve(__dirname,'src/styles') 110 | // 以前你可能这样引用 import "../../../styles/mixins.scss" 111 | // 现在你可以这样引用 import "style/mixins.scss" 112 | 113 | // 注意:别名只能在.js文件中使用。 114 | } 115 | }, 116 | 117 | module: { 118 | loaders: [ 119 | { 120 | test: /\.js$/, 121 | loaders: ['babel'], 122 | exclude: /node_modules/ 123 | }, 124 | { 125 | test: /\.scss$/, 126 | include: path.resolve(__dirname, 'src/js'), 127 | loaders: [ 128 | 'style', 129 | 'css?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]', 130 | 'postcss?parser=postcss-scss' 131 | ] 132 | }, 133 | // 组件样式,需要私有化,单独配置 134 | 135 | { 136 | test: /\.scss$/, 137 | include: path.resolve(__dirname, 'src/styles'), 138 | loader: 'style!css!postcss?parser=postcss-scss' 139 | }, 140 | // 公有样式,不需要私有化,单独配置 141 | 142 | { 143 | test: /\.css$/, 144 | include: path.resolve(__dirname, 'node_modules'), 145 | loader: 'style!css!postcss' 146 | }, 147 | 148 | { 149 | test: /\.(otf|eot|svg|ttf|woff|woff2).*$/, 150 | loader: 'url?limit=10000' 151 | }, 152 | { 153 | test: /\.(gif|jpe?g|png|ico)$/, 154 | loader: 'url?limit=10000' 155 | } 156 | ] 157 | }, 158 | postcss: function () { 159 | return [ 160 | require('precss'), 161 | require('autoprefixer'), 162 | require('rucksack-css'), 163 | pxtorem({ 164 | rootValue: 100, 165 | propWhiteList: [], 166 | }) 167 | ]; 168 | } 169 | }; -------------------------------------------------------------------------------- /step-04/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["latest", { 4 | "es2015": { 5 | "loose": true 6 | } 7 | }], 8 | "react", 9 | "stage-0" 10 | ], 11 | "plugins": [ 12 | "react-hot-loader/babel", 13 | ["import", { "style": "css", "libraryName": "antd-mobile" }], 14 | ["transform-runtime", { 15 | "helpers": false, 16 | "polyfill": false, 17 | "regenerator": true, 18 | "moduleName": "babel-runtime" 19 | }] 20 | ] 21 | } -------------------------------------------------------------------------------- /step-04/README.md: -------------------------------------------------------------------------------- 1 | # step-04 目前已完成,你可以完整运行 2 | step-04 是在 step-03 的基础上开发的 3 | step-04 主要围绕redux, 进行构建,做了四个基于redux的基础示例(简单,一般,复杂,异步)。 4 | 展示了在应用了redux的react项目中,组件之间的数据是如何获取,传递,并渲染到页面的。 5 | (该模板为React-Study最后一个系列,欢迎朋友们沟通交流,让前端开发变的更美好。) 6 | 7 | ## [DEMO](https://raw.githubusercontent.com/minooo/test/master/step-04-demo.gif) 8 | 点击上方DEMO预览 9 | 10 | ## 本模板包含step-03用到的所有包,下面将介绍 **额外** 添加的包 11 | 12 | #### [redux](https://github.com/reactjs/redux) [必需] 13 | > Redux 是 JavsScript 状态容器,提供可预测化的状态管理 14 | 安装:`npm install redux --save` 15 | 16 | --- 17 | 18 | #### [react-redux](https://github.com/reactjs/react-router) [强烈推荐] 19 | > 一个官方推出的,强大的灵活的将Redux绑定到React上的脚手架。 20 | 安装:`npm install react-redux --save` 21 | 22 | --- 23 | 24 | #### [redux-saga](https://github.com/yelouafi/redux-saga/) [强烈推荐] 25 | > 一个替代[redux-thunk](https://github.com/gaearon/redux-thunk)更优雅的用于管理Redux异步操作的中间件 26 | [redux-sage中文文档(繁体,同步)](https://neighborhood999.github.io/redux-saga/) 27 | 安装:`npm install redux-saga --save` 28 | 29 | --- 30 | 31 | #### [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) [强烈推荐] 32 | > Isomorphic WHATWG Fetch API, for Node & Browserify 33 | 安装:`npm install isomorphic-fetch --save` 34 | 35 | --- 36 | 37 | 38 | #### [redux-logger](https://github.com/evgenyrodionov/redux-logger) [开发需要] 39 | > 实时监控Redux的状态树的Store 40 | 打开浏览器调试F12,在 console 面板中可查看。 41 | 安装:`npm install redux-logger --save-dev` 42 | 43 | --- 44 | 45 | #### [react-fastclick](https://github.com/JakeSidSmith/react-fastclick) [可选] 46 | > 众所周知,移动设备屏幕的点击有300毫秒的延迟,这个库就是用来消除那300毫秒延迟的,从而 47 | 获得更加灵敏的点击体验。 48 | 安装:`npm install react-fastclick --save` 49 | 50 | --- 51 | #### [humps](https://github.com/domchristie/humps) [推荐] 52 | > 一个用来在字符串和对象键的驼峰写法的转换器。 53 | 安装:`npm install humps --save-dev` 54 | 55 | --- 56 | 57 | #### [lodash](https://github.com/lodash/lodash) [推荐] 58 | > 一个具有一致接口、模块化、高性能等特性的 JavaScript 工具库 59 | 安装:`npm install lodash --save-dev` 60 | 61 | --- 62 | 63 | #### [normalizr](https://github.com/paularmstrong/normalizr) [推荐] 64 | > 按照一个规范化模式来嵌套JSON 65 | 安装:`npm install normalizr --save-dev` 66 | 67 | --- 68 | 69 | #### [moment](https://github.com/moment/moment) [强烈推荐] 70 | > 一个专门针对日期进行解析,操作,验证,展示的非常强大好用的JS库 71 | 安装:`npm install moment --save` 72 | 73 | --- 74 | 75 | #### [iscroll](https://github.com/cubiq/iscroll/) [示例需要] 76 | > 一个高性能,小体积,无依赖,跨平台的使页面平滑滚动的JS库 77 | 安装:`npm install iscroll --save-dev` 78 | 79 | --- 80 | 81 | #### [react-iscroll](https://github.com/schovi/react-iscroll) [示例需要] 82 | > 一个对 iscroll 进行封装的 React 组件 83 | 安装:`npm install react-iscroll --save-dev` 84 | 85 | --- 86 | 87 | 88 | -------------------------------------------------------------------------------- /step-04/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "step-04", 3 | "version": "1.0.0", 4 | "description": "react react-router redux", 5 | "main": "index.js", 6 | "theme": "", 7 | "scripts": { 8 | "start": "npm-run-all --parallel open:src", 9 | "open:src": "babel-node server.js", 10 | "clean": "rimraf dist", 11 | "build:webpack": "webpack --config webpack-pro-config.js --progress --colors", 12 | "build": "npm-run-all clean build:webpack" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "react-router", 17 | "redux", 18 | "es6", 19 | "webpack", 20 | "npm" 21 | ], 22 | "author": "minooo", 23 | "license": "MIT", 24 | "dependencies": { 25 | "antd-mobile": "^0.9.12", 26 | "babel-runtime": "^6.20.0", 27 | "isomorphic-fetch": "^2.2.1", 28 | "moment": "^2.15.1", 29 | "react": "^15.4.1", 30 | "react-dom": "^15.4.1", 31 | "react-fastclick": "^2.1.2", 32 | "react-redux": "^5.0.1", 33 | "react-router": "^3.0.0", 34 | "redux": "^3.6.0", 35 | "redux-saga": "^0.12.0" 36 | }, 37 | "devDependencies": { 38 | "autoprefixer": "^6.4.1", 39 | "babel-cli": "^6.14.0", 40 | "babel-core": "^6.14.0", 41 | "babel-loader": "^6.2.5", 42 | "babel-plugin-import": "^1.0.1", 43 | "babel-plugin-transform-runtime": "^6.15.0", 44 | "babel-preset-latest": "^6.16.0", 45 | "babel-preset-react": "^6.11.1", 46 | "babel-preset-stage-0": "^6.5.0", 47 | "browser-sync": "^2.16.0", 48 | "chokidar": "^1.6.1", 49 | "classnames": "^2.2.5", 50 | "connect-history-api-fallback": "^1.3.0", 51 | "cross-env": "^2.0.1", 52 | "css-loader": "^0.25.0", 53 | "express": "^4.14.0", 54 | "file-loader": "^0.9.0", 55 | "html-webpack-plugin": "^2.22.0", 56 | "humps": "^1.1.0", 57 | "iscroll": "^5.2.0", 58 | "json-loader": "^0.5.4", 59 | "lodash": "^4.16.4", 60 | "normalizr": "^2.2.1", 61 | "npm-run-all": "^3.1.0", 62 | "postcss-loader": "^0.13.0", 63 | "postcss-pxtorem": "^3.3.1", 64 | "postcss-scss": "^0.3.0", 65 | "precss": "^1.4.0", 66 | "rc-form": "^1.0.0", 67 | "react-addons-css-transition-group": "^15.3.2", 68 | "react-hot-loader": "^3.0.0-beta.6", 69 | "react-iscroll": "^1.1.1", 70 | "redbox-react": "^1.3.1", 71 | "redux-logger": "^2.6.1", 72 | "rimraf": "^2.5.4", 73 | "rucksack-css": "^0.8.6", 74 | "style-loader": "^0.13.1", 75 | "url-loader": "^0.5.7", 76 | "webpack": "^1.13.2", 77 | "webpack-dev-middleware": "^1.8.2", 78 | "webpack-hot-middleware": "^2.12.2" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /step-04/server.js: -------------------------------------------------------------------------------- 1 | // This file configures the development web server 2 | // which supports hot reloading and synchronized testing. 3 | 4 | // Require Browsersync along with webpack and middleware for it 5 | import browserSync from 'browser-sync'; 6 | // Required for react-router browserHistory 7 | // see https://github.com/BrowserSync/browser-sync/issues/204#issuecomment-102623643 8 | import historyApiFallback from 'connect-history-api-fallback'; 9 | import webpack from 'webpack'; 10 | import webpackDevMiddleware from 'webpack-dev-middleware'; 11 | import webpackHotMiddleware from 'webpack-hot-middleware'; 12 | import config from './webpack-dev-config'; 13 | 14 | const bundler = webpack(config); 15 | 16 | // Run Browsersync and use middleware for Hot Module Replacement 17 | browserSync({ 18 | port: 8888, 19 | ui: { 20 | port: 8889 21 | }, 22 | server: { 23 | baseDir: 'src', 24 | 25 | middleware: [ 26 | historyApiFallback(), 27 | 28 | webpackDevMiddleware(bundler, { 29 | // Dev middleware can't access config, so we provide publicPath 30 | publicPath: config.output.publicPath, 31 | 32 | // These settings suppress noisy webpack output so only errors are displayed to the console. 33 | noInfo: false, 34 | quiet: false, 35 | stats: { 36 | assets: false, 37 | colors: true, 38 | version: false, 39 | hash: false, 40 | timings: false, 41 | chunks: false, 42 | chunkModules: false 43 | }, 44 | 45 | // for other settings see 46 | // http://webpack.github.io/docs/webpack-dev-middleware.html 47 | }), 48 | 49 | // bundler should be the same as above 50 | webpackHotMiddleware(bundler) 51 | ] 52 | }, 53 | 54 | // no need to watch '*.js' here, webpack will take care of it for us, 55 | // including full page reloads if HMR won't work 56 | files: [ 57 | 'src/*.html' 58 | ] 59 | }); 60 | -------------------------------------------------------------------------------- /step-04/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/favicon.ico -------------------------------------------------------------------------------- /step-04/src/fonts/dudu.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/fonts/dudu.eot -------------------------------------------------------------------------------- /step-04/src/fonts/dudu.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/fonts/dudu.ttf -------------------------------------------------------------------------------- /step-04/src/fonts/dudu.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/fonts/dudu.woff -------------------------------------------------------------------------------- /step-04/src/images/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/images/big.png -------------------------------------------------------------------------------- /step-04/src/images/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minooo/React-Study/d243077c733cccf087699025e53e5ff67cc1c692/step-04/src/images/small.png -------------------------------------------------------------------------------- /step-04/src/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta http-equiv="X-UA-Compatible" content="IE=edge"> 5 | <meta charset="utf-8"/> 6 | <meta name="Cache-Control" content="private"> 7 | <!--阿里移动端高清方案代码 详情 https://github.com/minooo/React-Study/tree/master/step-03 --> 8 | <script>!function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=window;t["default"]=i.flex=function(e,t){var a=e||100,n=t||1,r=i.document,o=navigator.userAgent,d=o.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i),l=o.match(/U3\/((\d+|\.){5,})/i),c=l&&parseInt(l[1].split(".").join(""),10)>=80,p=navigator.appVersion.match(/(iphone|ipad|ipod)/gi),s=i.devicePixelRatio||1;p||d&&d[1]>534||c||(s=1);var u=1/s,m=r.querySelector('meta[name="viewport"]');m||(m=r.createElement("meta"),m.setAttribute("name","viewport"),r.head.appendChild(m)),m.setAttribute("content","width=device-width,user-scalable=no,initial-scale="+u+",maximum-scale="+u+",minimum-scale="+u),r.documentElement.style.fontSize=a/2*s*n+"px"},e.exports=t["default"]}]); 9 | flex(100, 1);</script> 10 | <title><%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /step-04/src/js/actions/CounterActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | INCREMENT_COUNTER, 3 | DECREMENT_COUNTER, 4 | INCREMENT_ASYNC 5 | } from './actionsTypes' 6 | 7 | export const increment = () => ({ type: INCREMENT_COUNTER }) 8 | 9 | export const decrement = () => ({ type: DECREMENT_COUNTER }) 10 | 11 | export const onIncrementAsync = () => ({ type: INCREMENT_ASYNC }) -------------------------------------------------------------------------------- /step-04/src/js/actions/PostActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | REQUEST_POSTS, 3 | RECEIVE_POSTS 4 | } from './actionsTypes' 5 | 6 | export const onRequestPosts = () => ({type: REQUEST_POSTS}) 7 | export const onReceivePosts = (post) => ({ 8 | type: RECEIVE_POSTS, 9 | posts 10 | }) -------------------------------------------------------------------------------- /step-04/src/js/actions/TimerActions.js: -------------------------------------------------------------------------------- 1 | import { START, STOP, RESET } from './actionsTypes' 2 | 3 | export const onStart = () => ({ type: START }) 4 | export const onStop = () => ({ type: STOP }) 5 | export const onReset = () => ({ type: RESET }) -------------------------------------------------------------------------------- /step-04/src/js/actions/TodoActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_TODO, 3 | COMPLETE_TODO, 4 | DELETE_TODO, 5 | EDIT_TODO, 6 | CLEAR_COMPLETED, 7 | COMPLETE_ALL 8 | } from './actionsTypes' 9 | 10 | export const addTodo = text => ({ type: ADD_TODO, text }) 11 | 12 | export const completeTodo = id => ({ type: COMPLETE_TODO, id}) 13 | 14 | export const deleteTodo = id => ({ type: DELETE_TODO, id }) 15 | 16 | export const editTodo = (id, text) => ({ type: EDIT_TODO, id, text }) 17 | 18 | export const clearCompleted = () => ({ type: CLEAR_COMPLETED }) 19 | 20 | export const completeAll = () => ({ type: COMPLETE_ALL }) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /step-04/src/js/actions/actionsTypes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 这里定义所有的action类型 3 | * */ 4 | 5 | // Counter 6 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; 7 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'; 8 | export const INCREMENT_ASYNC = 'INCREMENT_ASYNC' 9 | export const COUNT_DOWN = 'COUNT_DOWN' 10 | export const ASYNC_OVER = 'ASYNC_OVER' 11 | 12 | // Todos 13 | export const ADD_TODO = 'ADD_TODO' 14 | export const COMPLETE_TODO = 'COMPLETE_TODO' 15 | export const DELETE_TODO = 'DELETE_TODO' 16 | export const EDIT_TODO = 'EDIT_TODO' 17 | export const COMPLETE_ALL = 'COMPLETE_ALL' 18 | export const CLEAR_COMPLETED = 'CLEAR_COMPLETED' 19 | 20 | // TodoFilters 21 | export const SHOW_ALL = 'show_all' 22 | export const SHOW_COMPLETED = 'show_completed' 23 | export const SHOW_ACTIVE = 'show_active' 24 | 25 | // SHOW_CONGRATULATION 26 | export const SHOW_CONGRATULATION = 'show_congratulation' 27 | 28 | // Timer 29 | export const START = 'START' 30 | export const STOP = 'STOP' 31 | export const RESET = 'RESET' 32 | export const TIMER = 'TIMER' 33 | 34 | // Post 35 | export const REQUEST_POSTS = 'REQUEST_POSTS' 36 | export const RECEIVE_POSTS = 'RECEIVE_POSTS' 37 | export const SELECT_REDDIT = 'SELECT_REDDIT' 38 | export const INVALIDATA_REDDIT = 'INVALIDATA_REDDIT' -------------------------------------------------------------------------------- /step-04/src/js/components/Counter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './index.scss' 3 | 4 | export default class Counter extends React.Component { 5 | 6 | incrementIfOdd = () => { 7 | if(this.props.counter %2 ===0) { 8 | this.props.increment() 9 | } 10 | } 11 | 12 | render() { 13 | const { counter, increment, decrement, onIncrementAsync, asyncBool, asyncCountDown } = this.props 14 | return ( 15 |
16 |
17 |

{counter}

18 |
19 | 25 | 26 | 32 | 33 | 39 | 40 | 47 |
48 |
49 |
50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /step-04/src/js/components/Counter/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../../styles/mixins.scss"; 2 | .root { 3 | position: absolute; 4 | top: 0; 5 | bottom: 0; 6 | left: 0; 7 | right: 0; 8 | padding-bottom: 1.2rem; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | background: linear-gradient(-150deg, #2ae0b3, #26CAA1); 13 | } 14 | 15 | .wrap { 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | flex-direction: column; 20 | font-size: 180%; 21 | border-radius: 0.04rem; 22 | background: #fefefe; 23 | width: 4rem; 24 | height: 6rem; 25 | text-align: center; 26 | box-shadow: 1.06066px 1.06066px 0 rgba(27, 183, 145, 0.794), 2.12132px 2.12132px 0 rgba(27, 183, 145, 0.788), 3.18198px 3.18198px 0 rgba(27, 183, 145, 0.782), 4.24264px 4.24264px 0 rgba(27, 183, 145, 0.776), 5.3033px 5.3033px 0 rgba(27, 183, 145, 0.77), 6.36396px 6.36396px 0 rgba(27, 184, 145, 0.764), 7.42462px 7.42462px 0 rgba(27, 184, 146, 0.758), 8.48528px 8.48528px 0 rgba(27, 184, 146, 0.752), 9.54594px 9.54594px 0 rgba(28, 184, 146, 0.746), 10.6066px 10.6066px 0 rgba(28, 184, 146, 0.74), 11.66726px 11.66726px 0 rgba(28, 184, 146, 0.734), 12.72792px 12.72792px 0 rgba(28, 184, 146, 0.728), 13.78858px 13.78858px 0 rgba(28, 184, 146, 0.722), 14.84924px 14.84924px 0 rgba(28, 184, 146, 0.716), 15.9099px 15.9099px 0 rgba(28, 184, 146, 0.71), 16.97056px 16.97056px 0 rgba(28, 185, 146, 0.704), 18.03122px 18.03122px 0 rgba(28, 185, 146, 0.698), 19.09188px 19.09188px 0 rgba(28, 185, 147, 0.692), 20.15254px 20.15254px 0 rgba(28, 185, 147, 0.686), 21.2132px 21.2132px 0 rgba(28, 185, 147, 0.68), 22.27386px 22.27386px 0 rgba(28, 185, 147, 0.674), 23.33452px 23.33452px 0 rgba(28, 185, 147, 0.668), 24.39518px 24.39518px 0 rgba(29, 185, 147, 0.662), 25.45584px 25.45584px 0 rgba(29, 185, 147, 0.656), 26.5165px 26.5165px 0 rgba(29, 186, 147, 0.65), 27.57716px 27.57716px 0 rgba(29, 186, 147, 0.644), 28.63782px 28.63782px 0 rgba(29, 186, 148, 0.638), 29.69848px 29.69848px 0 rgba(29, 186, 148, 0.632), 30.75914px 30.75914px 0 rgba(29, 186, 148, 0.626), 31.81981px 31.81981px 0 rgba(29, 186, 148, 0.62), 32.88047px 32.88047px 0 rgba(29, 186, 148, 0.614), 33.94113px 33.94113px 0 rgba(29, 186, 148, 0.608), 35.00179px 35.00179px 0 rgba(29, 187, 148, 0.602), 36.06245px 36.06245px 0 rgba(30, 187, 148, 0.596), 37.12311px 37.12311px 0 rgba(30, 187, 149, 0.59), 38.18377px 38.18377px 0 rgba(30, 187, 149, 0.584), 39.24443px 39.24443px 0 rgba(30, 187, 149, 0.578), 40.30509px 40.30509px 0 rgba(30, 187, 149, 0.572), 41.36575px 41.36575px 0 rgba(30, 188, 149, 0.566), 42.42641px 42.42641px 0 rgba(30, 188, 149, 0.56), 43.48707px 43.48707px 0 rgba(30, 188, 149, 0.554), 44.54773px 44.54773px 0 rgba(30, 188, 150, 0.548), 45.60839px 45.60839px 0 rgba(30, 188, 150, 0.542), 46.66905px 46.66905px 0 rgba(31, 188, 150, 0.536), 47.72971px 47.72971px 0 rgba(31, 189, 150, 0.53), 48.79037px 48.79037px 0 rgba(31, 189, 150, 0.524), 49.85103px 49.85103px 0 rgba(31, 189, 150, 0.518), 50.91169px 50.91169px 0 rgba(31, 189, 151, 0.512), 51.97235px 51.97235px 0 rgba(31, 189, 151, 0.506), 53.03301px 53.03301px 0 rgba(31, 190, 151, 0.5), 54.09367px 54.09367px 0 rgba(32, 190, 151, 0.494), 55.15433px 55.15433px 0 rgba(32, 190, 151, 0.488), 56.21499px 56.21499px 0 rgba(32, 190, 152, 0.482), 57.27565px 57.27565px 0 rgba(32, 190, 152, 0.476), 58.33631px 58.33631px 0 rgba(32, 191, 152, 0.47), 59.39697px 59.39697px 0 rgba(32, 191, 152, 0.464), 60.45763px 60.45763px 0 rgba(32, 191, 152, 0.458), 61.51829px 61.51829px 0 rgba(33, 191, 153, 0.452), 62.57895px 62.57895px 0 rgba(33, 192, 153, 0.446), 63.63961px 63.63961px 0 rgba(33, 192, 153, 0.44), 64.70027px 64.70027px 0 rgba(33, 192, 153, 0.434), 65.76093px 65.76093px 0 rgba(33, 193, 154, 0.428), 66.82159px 66.82159px 0 rgba(34, 193, 154, 0.422), 67.88225px 67.88225px 0 rgba(34, 193, 154, 0.416), 68.94291px 68.94291px 0 rgba(34, 193, 155, 0.41), 70.00357px 70.00357px 0 rgba(34, 194, 155, 0.404), 71.06423px 71.06423px 0 rgba(34, 194, 155, 0.398), 72.12489px 72.12489px 0 rgba(35, 194, 155, 0.392), 73.18555px 73.18555px 0 rgba(35, 195, 156, 0.386), 74.24621px 74.24621px 0 rgba(35, 195, 156, 0.38), 75.30687px 75.30687px 0 rgba(35, 196, 156, 0.374), 76.36753px 76.36753px 0 rgba(36, 196, 157, 0.368), 77.42819px 77.42819px 0 rgba(36, 196, 157, 0.362), 78.48885px 78.48885px 0 rgba(36, 197, 157, 0.356), 79.54951px 79.54951px 0 rgba(36, 197, 158, 0.35), 80.61017px 80.61017px 0 rgba(37, 198, 158, 0.344), 81.67083px 81.67083px 0 rgba(37, 198, 159, 0.338), 82.73149px 82.73149px 0 rgba(37, 199, 159, 0.332), 83.79215px 83.79215px 0 rgba(38, 199, 160, 0.326), 84.85281px 84.85281px 0 rgba(38, 200, 160, 0.32), 85.91347px 85.91347px 0 rgba(38, 200, 160, 0.314), 86.97413px 86.97413px 0 rgba(39, 201, 161, 0.308), 88.03479px 88.03479px 0 rgba(39, 201, 161, 0.302), 89.09545px 89.09545px 0 rgba(39, 202, 162, 0.296), 90.15611px 90.15611px 0 rgba(40, 202, 163, 0.29), 91.21677px 91.21677px 0 rgba(40, 203, 163, 0.284), 92.27743px 92.27743px 0 rgba(41, 204, 164, 0.278), 93.3381px 93.3381px 0 rgba(41, 204, 164, 0.272), 94.39876px 94.39876px 0 rgba(42, 205, 165, 0.266), 95.45942px 95.45942px 0 rgba(42, 206, 166, 0.26), 96.52008px 96.52008px 0 rgba(43, 207, 166, 0.254), 97.58074px 97.58074px 0 rgba(43, 207, 167, 0.248), 98.6414px 98.6414px 0 rgba(44, 208, 168, 0.242), 99.70206px 99.70206px 0 rgba(45, 209, 169, 0.236), 100.76272px 100.76272px 0 rgba(45, 210, 170, 0.23), 101.82338px 101.82338px 0 rgba(46, 211, 171, 0.224), 102.88404px 102.88404px 0 rgba(47, 212, 172, 0.218), 103.9447px 103.9447px 0 rgba(47, 214, 173, 0.212), 105.00536px 105.00536px 0 rgba(48, 215, 174, 0.206), 106.06602px 106.06602px 0 rgba(49, 216, 175, 0.2);} 27 | 28 | .counter { 29 | font-size: 2rem; 30 | line-height: 1; 31 | } 32 | 33 | .btnCommon { 34 | padding: 0.20em 0.4em; 35 | line-height: 1; 36 | vertical-align: middle; 37 | border-radius: 3px; 38 | color: #fefefe; 39 | border: 3px solid; 40 | background: #fefefe; 41 | margin-top: 10px; 42 | margin-bottom: 10px; 43 | transition: transform 60ms ease-out; 44 | &:active { 45 | transform: translateY(2px); 46 | transition: transform 100ms ease-out; 47 | } 48 | &:hover, 49 | &:focus { 50 | outline: none; 51 | } 52 | } 53 | 54 | .increment, .decrement { 55 | composes: btnCommon; 56 | width: 1rem; 57 | } 58 | 59 | .increment { 60 | color: #1abc9c; 61 | border-color: #1abc9c; 62 | margin-right: 0.2rem; 63 | } 64 | 65 | .decrement { 66 | color: tomato; 67 | border-color: tomato; 68 | } 69 | 70 | .ifOdd, .async { 71 | composes: btnCommon; 72 | } 73 | 74 | .ifOdd { 75 | color: #da964c; 76 | border-color: #da964c; 77 | } 78 | 79 | .async { 80 | color: #37bae5; 81 | border-color: #37bae5; 82 | } 83 | 84 | .async[disabled] { 85 | color: #ccc; 86 | border-color: #ccc; 87 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Nav/NavLink/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import styles from './index.scss' 4 | 5 | export default class NavLink extends Component { 6 | render() { 7 | const { pathUrl, active, icoName, linkName, ...rest } = this.props; 8 | return ( 9 | 15 | 16 | {linkName} 17 | 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Nav/NavLink/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | flex: auto; 3 | display: flex; 4 | flex-direction: column; 5 | flex-wrap: wrap; 6 | justify-content: center; 7 | align-items: center; 8 | line-height: 1; 9 | color: #666; 10 | &:hover { 11 | color: #1abc9c; 12 | } 13 | } 14 | 15 | .ico { 16 | font-size: 0.42rem; 17 | display: block; 18 | margin-bottom: 0.1rem; 19 | } 20 | 21 | .text { 22 | display: block; 23 | line-height: 1; 24 | } 25 | -------------------------------------------------------------------------------- /step-04/src/js/components/Nav/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import NavLink from './NavLink' 3 | import styles from './index.scss' 4 | 5 | export default class Nav extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 |
11 | 18 | 24 | 30 | 36 |
37 |
38 | ) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /step-04/src/js/components/Nav/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | background-color: #f5f5f5; 3 | height: 1rem; 4 | position: fixed; 5 | z-index: 50; 6 | bottom: 0; 7 | left: 0; 8 | width: 100%; 9 | display: flex; 10 | border-top: 1px solid #ccc; 11 | } 12 | 13 | .navOne, .navTwo, .navThree, .navFour, .navFive { 14 | color: #1abc9c; 15 | &:hover, &:active { 16 | color: #1abc9c; 17 | } 18 | } 19 | .navOne i:before { 20 | content: "\e93d"; 21 | } 22 | 23 | .navTwo i:before { 24 | content: "\e917"; 25 | } 26 | 27 | .navThree i:before { 28 | content: "\e936"; 29 | } 30 | 31 | .navFour i:before { 32 | content: "\e939"; 33 | } 34 | 35 | .navFive i:before { 36 | content: "\e913"; 37 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Post/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | const Post = props => { 4 | return ( 5 |
6 | { 7 | props.items.length>0 &&
8 | { 9 | props.items.map((item, index) => 10 |
11 | 16 |
17 |

昵称:{item.login}

18 |

19 | 主页: 20 | 我的 Github 21 |

22 |

粉丝:{item.followers}

23 |
24 |
25 | ) 26 | } 27 |
28 | } 29 |
30 | ) 31 | } 32 | 33 | export default Post -------------------------------------------------------------------------------- /step-04/src/js/components/Post/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | /* background: #fff;*/ 3 | padding: 0.3rem 0.2rem; 4 | } 5 | .list { 6 | display: flex; 7 | padding-top: 0.2rem; 8 | padding-bottom: 0.2rem; 9 | border-bottom: 1px solid #ddd; 10 | } 11 | .img { 12 | display: inline-block; 13 | width: 1rem; 14 | height: 1rem; 15 | } 16 | .con { 17 | margin-left: 0.2rem; 18 | } 19 | .link { 20 | color: #1abc9c; 21 | text-decoration: underline!important; 22 | } 23 | 24 | .rCommon { 25 | position: absolute; 26 | z-index: 10; 27 | height: 0.6rem; 28 | width: 100%; 29 | background: #f2f2f2; 30 | } 31 | 32 | 33 | .refresh { 34 | composes: rCommon; 35 | top: -0.6rem; 36 | } 37 | .refresh2 { 38 | composes: rCommon; 39 | top: 0; 40 | } -------------------------------------------------------------------------------- /step-04/src/js/components/ScrollBox/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react' 2 | import ReactIScroll from 'react-iscroll' 3 | import iScroll from 'iscroll/build/iscroll-probe' 4 | 5 | /* 6 | const ScrollBox = ({children, ...rest}) => { 7 | return ( 8 | 12 | {children} 13 | 14 | ) 15 | } 16 | 17 | export default ScrollBox*/ 18 | 19 | export default class ScrollBox extends Component { 20 | render() { 21 | const {children, ...rest} = this.props 22 | return ( 23 | 27 | {children} 28 | 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /step-04/src/js/components/Timer/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import moment from 'moment' 3 | import styles from './index.scss' 4 | 5 | const Timer = props => { 6 | return ( 7 |
8 |

9 | {moment(props.seconds*1000).format("mm:ss")} 10 | ({props.status}) 11 |

12 | 13 | 20 | 21 | 28 | 29 | 36 |
37 | ) 38 | } 39 | 40 | export default Timer -------------------------------------------------------------------------------- /step-04/src/js/components/Timer/index.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | color: #fff; 3 | border: 1px solid #1abc9c; 4 | border-radius: 3px; 5 | padding: 15px 28px; 6 | margin: 15px 25px 0 0; 7 | background: #1abc9c; 8 | } 9 | .btn[disabled] { 10 | color: #ccc; 11 | border-color: #ccc; 12 | background: #fff; 13 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Todos/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import classnames from 'classnames' 3 | import { Button } from 'antd-mobile' 4 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../../actions/actionsTypes' 5 | 6 | import styles from './index.scss' 7 | 8 | const FILTER_TITLES = { 9 | [SHOW_ALL]: '全部', 10 | [SHOW_ACTIVE]: '未完成', 11 | [SHOW_COMPLETED]: '已完成' 12 | } 13 | 14 | export default class Footer extends Component { 15 | renderFilterLink(filter) { 16 | const title = FILTER_TITLES[filter] 17 | const { selectedFilter, onShow } = this.props 18 | return ( 19 | onShow(filter)} 22 | > 23 | {title} 24 | 25 | ) 26 | } 27 | 28 | renderTodoCount() { 29 | const { uncompletedCount } = this.props 30 | const itemword = uncompletedCount > 0 ? uncompletedCount + ' 个任务未完成' : '目前没有任务' 31 | 32 | return ( 33 | 34 | {itemword} 35 | 36 | ) 37 | } 38 | 39 | renderClearButton() { 40 | const { completedCount, onClearCompleted } = this.props 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 | {this.renderTodoCount()} 57 |
    58 | {[SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE].map(item => 59 |
  • 60 | {this.renderFilterLink(item)} 61 |
  • 62 | )} 63 |
64 | {this.renderClearButton()} 65 |
66 | ) 67 | } 68 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Todos/Footer/index.scss: -------------------------------------------------------------------------------- 1 | .selected { 2 | color: #1abc9c; 3 | } 4 | 5 | .filters { 6 | margin: 20px 0; 7 | font-size: 0.28rem; 8 | } 9 | 10 | .footer { 11 | margin-top: 0.28rem; 12 | } 13 | 14 | .clearBtn { 15 | font-size: 0.3rem; 16 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Todos/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styles from './index.scss' 3 | import TodoTextInput from '../TodoTextInput' 4 | 5 | export default class Header extends Component { 6 | 7 | handleSave = text => { 8 | if (text.length !== 0) { 9 | this.props.addTodo(text) 10 | } 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 |

记事本

17 | 22 |
23 | ) 24 | } 25 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Todos/Header/index.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 100%; 3 | } 4 | .title { 5 | font-size: 3em; 6 | line-height: 1; 7 | text-align: center; 8 | margin-bottom: 0.4em; 9 | } -------------------------------------------------------------------------------- /step-04/src/js/components/Todos/MainSection/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import TodoItem from '../TodoItem' 3 | import Footer from '../Footer' 4 | import { Switch, NoticeBar } from 'antd-mobile' 5 | import { createForm } from 'rc-form' 6 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../../../actions/actionsTypes' 7 | 8 | import styles from './index.scss' 9 | 10 | const TODO_FILTERS = { 11 | [SHOW_ALL]: () => true, 12 | [SHOW_COMPLETED]: item => item.completed, 13 | [SHOW_ACTIVE]: item => !item.completed 14 | } 15 | 16 | class MainSection extends Component { 17 | state = { 18 | filter: SHOW_ALL 19 | } 20 | 21 | handleShow = filter => { 22 | this.setState({filter}) 23 | } 24 | 25 | renderToggleAll(completedCount) { 26 | const { todos, completeAll } = this.props 27 | const { getFieldProps } = this.props.form 28 | if (todos.length > 0) { 29 | return ( 30 | 38 | ) 39 | } 40 | } 41 | 42 | renderFooter(completedCount) { 43 | const { todos } = this.props 44 | const { filter } = this.state 45 | const activeCount = todos.length - completedCount 46 | 47 | if(todos.length) { 48 | return ( 49 |