欢迎移步个人博客阅读:《React 入门实践》css
在写这篇文章以前,我已经接触 React 有大半年了。在初步学习 React 以后就正式应用到项目中,当时就想把本身的一些想法写出来分享一下,无奈不太会写文章,再则时间不是很充裕,因此也就搁下了。html
本篇文章比较基础,没有深刻的分析,大神们轻看。废话就很少说了,那么让咱们来进入正题。node
首先想要介绍的是 React,看到这篇文章的朋友想必都有一些关于 React 的了解了,但对于刚接触的新人而言,在这就要简要地介绍一下了。而后就是关于使用 React 构建一个简单单页应用(下文用 SPA 代替,Single Page Application)的一些介绍和讲解。react
React 起源于 Facebook 的内部项目,由于该公司对市场上全部 JavaScript MVC 框架,都不满意,就决定本身写一套,用来架设Instagram 的网站。作出来之后,发现这套东西很好用,就在2013年5月开源了。(更多相关介绍请看这)webpack
特色:git
仅仅只是 UIgithub
虚拟 DOM:最大限度减小与 DOM 的交互(相似于使用 jQuery 操做 DOM)web
单向数据流:很大程度减小了重复代码的使用express
组件化:npm
可组合(Composeable):一个组件易于和其它组件一块儿使用,或者嵌套在另外一个组件内部。若是一个组件内部建立了另外一个组件,那么说父组件拥有(own)它建立的子组件,经过这个特性,一个复杂的UI能够拆分红多个简单的UI组件
可重用(Reusable):每一个组件都是具备独立功能的,它能够被使用在多个UI场景
可维护(Maintainable):每一个小的组件仅仅包含自身的逻辑,更容易被理解和维护
生命周期:
Mounting:已插入真实 DOM
Updating:正在被从新渲染
Unmounting:已移出真实 DOM
React 为每一个状态都提供了两种处理函数,will 函数在进入状态以前调用,did 函数在进入状态以后调用,三种状态共计五种处理函数。
componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
此外,React 还提供两种特殊状态的处理函数。
componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
shouldComponentUpdate(object nextProps, object nextState):组件判断是否从新渲染时调用
那么进入正题,花了点时间去写一个简单的 SPA,也算是一个比较完整 React 骨架,但不包括测试(测试的教程能够看这个),相关源码能够查看 react-start-kit。
接下来看看咱们这个项目的构建须要用到些什么:
react
redux
webpack
react-router
ant design
babel
...
还有一些没有列举出来,具体能够看仓库源码的 package.json
。其中的详细介绍会在文尾列出一些我所看过的文章或是官方介绍。
说到 React 项目的构建就不得不提 Webpack 这个神器。构建工具备不少,例如 Grunt,Gulp,Brunch 等,相比这些构建工具,Webpack 感受就是和 React 不谋而合,尤为是 react-hot-loader 这样的神器(热加载),让 Webpack 成为最主流的 React 构建工具。
关于 Webpack 的特性以及介绍这里就不赘述了,咱们能够从下图看出 Webpack 的做用:
接着咱们从项目代码中来看 Webpack。
entry: { app: [__dirname + '/src/index'], }, output: { path: __dirname + '/_dist', filename: '[name]_[hash].js', }
这部分主要是指定入口和出口文件。entry
做为项目的入口文件;output
做为文件编译后的出口,其中 path
表明输出的路径,filename
表明文件名称,而 [name]_[hash]
保证了浏览器不会存在缓存(即修改文件后效果不生效)。
module: { loaders: [{ test: /\.js$/, loaders: ['babel'], exclude: /node_modules/, }, { test: /\.css$/, loaders: ['style', 'css'], include: /components/, }, { test: /\.(jpe?g|png|gif|svg|ico)/i, loader: 'file', }, { test: /\.(ttf|eot|svg|woff|woff2)/, loader: 'file', }, { test: /\.(pdf)/, loader: 'file', }, { test: /\.(swf|xap)/, loader: 'file', }], }
而这部分会帮助咱们去处理不一样类型的文件,其中 test
就是文件的后缀,loaders
是“转译器”,include
是指定文件的目录,exclude
是排除某个目录。咱们能够看出,全部的 .js
文件都会经过 babel 去转译,也就是咱们在项目中使用 ES6+ 语法会经过 babel 转译成浏览器能够识别的 ES5 代码。
最后配置好的 config 是这样的:
var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { app: [__dirname + '/src/index'], }, output: { path: __dirname + '/_dist', filename: '[name]_[hash].js', }, resolve: { root: [ __dirname + '/src', __dirname + '/node_modules', __dirname, ], extensions: ['', '.js'], }, module: { loaders: [{ test: /\.js$/, loaders: ['babel'], exclude: /node_modules/, }, { test: /\.css$/, loaders: ['style', 'css'], include: /components/, }, { test: /\.(jpe?g|png|gif|svg|ico)/i, loader: 'file', }, { test: /\.(ttf|eot|svg|woff|woff2)/, loader: 'file', }, { test: /\.(pdf)/, loader: 'file', }, { test: /\.(swf|xap)/, loader: 'file', }], }, plugins: [ new HtmlWebpackPlugin({ template: __dirname + '/src/index.html', favicon: __dirname + '/src/favicon.ico', inject: false, }), ], };
Node.js web 应用开发框架 Express 做为项目的 web 服务器,有 Node.js 开发经验的同窗应该挺熟悉的了,这里也很少作赘述。
最终的启动代码是这样的:
var express = require('express'); var webpack = require('webpack'); var webpackConfig = require('./webpack.development'); var app = express(); var compiler = webpack(webpackConfig); app.use(require('webpack-dev-middleware')(compiler, { stats: { colors: true, }, })); app.use(require('webpack-hot-middleware')(compiler)); //热加载 app.listen(process.env.PORT, function(err) { //在没有端口的状况下,会自动给出一个随机端口 if (err) { console.log(err); } });
为了方便咱们的访问,项目使用了 minihost
进行启动,方便快捷。值得一提的是,使用 h -- npm start
命令启动时,访问的是项目文件夹的名称做为连接,例如项目叫 myproject
,那么此时能够访问 myproject.t.t
。
对于复杂的 SPA,状态(state)管理很是重要。state 可能包括:服务端的响应数据、本地对响应数据的缓存、本地建立的数据(好比,表单数据)以及一些 UI 的状态信息(好比,路由、选中的 tab、是否显示下拉列表、页码控制等等)。若是 state 变化不可预测,就会难于调试(state 不易重现,很难复现一些 bug)和不易于扩展(好比,优化更新渲染、服务端渲染、路由切换时获取数据等等)。
state 为单一对象,使得 Redux 只须要维护一棵状态树,服务端很容易初始化状态,易于服务器渲染。state 只能经过 dispatch(action) 来触发更新,更新逻辑由 reducer 来执行。
在使用 Redux 后,state 就变得很容易维护,并且数据流很是清晰,容易解决遇到的 BUG。
咱们能够看下图来简要地理解 Redux:
咱们能够在项目中看到的结构是:
├─store ├─actions ├─reducers ├─constants ├─helpers ├─components ├─app.js ├─favicon.ico ├─index.html ├─index.js └─routes.js
最终咱们的 store 是:
import {createStore, applyMiddleware, combineReducers, compose} from 'redux'; import thunk from 'redux-thunk'; import {reduxReactRouter} from 'redux-router'; import createHistory from 'history/lib/createHashHistory'; import routes from '../routes'; import * as reducers from '../reducers'; let middlewares = [thunk]; if (process.env.NODE_ENV === 'development') { //在开发环境下能够看到 state 的 log const logger = require('redux-logger'); middlewares = [...middlewares, logger]; } const finalCreateStore = compose( //组合多个函数 applyMiddleware(...middlewares), reduxReactRouter({routes, createHistory}), )(createStore); //建立 store 来管理全部的 state export default function configureStore(initialState) { const reducer = combineReducers(reducers); //把一个由多个不一样 reducer 函数做为 value 的 object,合并成一个最终的 reducer 函数 const store = finalCreateStore(reducer, initialState); if (process.env.NODE_ENV === 'development' && module.hot) { //开发环境下的热加载 module.hot.accept('../reducers', () => { const nextReducers = require('../reducers'); const nextReducer = combineReducers(nextReducers); store.replaceReducer(nextReducer); }); } return store; }
获取 state 须要在组件中调用 connect
函数,能够自行定义须要获取的 state。(这用于区分展现型和容器型组件)
... @connect( state => ({ data: state.data }) ) export default class ComponentOne extends Component { ... }
注意:connect
必须紧跟 component 的定义,否则会报错。
为项目添加路由系统,使用了 react-router 来管理路由。在开发项目的时候,比较推荐的作法是使用路由去跳转页面,而且建立 store 的同时咱们就把 router 加入其中,而后咱们根据路由的变化去更新视图。
咱们能够看看路由的源码:
import React from 'react'; import Route from 'react-router/lib/Route'; //import {Route} from 'react-router'; import Base from 'components/base/Base'; import Home from 'components/home/Home'; export default ( <Route component={Base}> <Route path="/" component={Home} /> <Route path="/home" component={Home} /> </Route> );
path
是跳转路径,component
是与路径相匹配的组件。
由蚂蚁金服技术部出品的一个 UI 设计语言,也是项目中所用到的 UI 组件库。
特性:
Designed as Ant Design,提炼和服务企业级中后台产品的交互语言和视觉风格
React Component 上精心封装的高质量 UI 库
基于 npm + webpack + babel 的工做流,支持 ES2015
选择理由:
有很好的技术支持
简洁的样式
基本涵盖经常使用组件
...
组件做为 React 渲染的一个基本组成,咱们一般把它们分为两类,容器型和展现型。相较于容器型,展现型是经过容器型传递 props 来获取数据,而容器型能够直接从 store 中获取,处理并传递给下级组件。
在实际应用中会发现,定义一个容器型组件负责处理数据,而后分发给下级展现型组件,当须要更新数据时,那么容器型组件发生变化会引发下级展现型组件的变化,这样就对咱们业务上形成了必定的困扰(在不须要更新的部分组件上也发生了更新)。所以,咱们选择在须要获取数据的组件中使用 connect
,这样则会方便不少(感受有些违反规则)。
在项目中咱们会这么定义组件:
import React, {Component} from 'react'; import {connect} from 'react-redux'; import Presentational from 'components/common/Presentational'; @connect( state => ({ data: state.data }) ) export default class Container extends Component { render() { const {data} = this.props; return ( <Presentational data={data} /> ) } }
上面是能够从 store 获取数据的组件,并嵌套另外一个组件,将数据传递给它。
import React, {Component, PropTypes} from 'react'; export default class Presentational extends Component { static propTypes = { data: PropTypes.string, } render() { const {data} = this.props; return ( <div> {data} </div> ) } }
获取上一个组件传递过来的数据,并展现出来。
这是一篇科普文(哈哈~囧),并无深刻去分析各项技术的具体内容,但愿能帮助刚入手 React 的新手们。实践项目的源码能够在 react-start-kit 看到,你能够下载这个项目进行本身的一些探索和开发。还在努力探索中,文中有措辞不当或是疏漏,欢迎提出意见和建议。
react 官网
Babel 官网
redux 介绍
redux 中文文档
Ant design 官网
React 入门实例教程
react-router 中文文档
Webpack 傻瓜式指南(一)
CSS Modules 详解及 React 中实践
一看就懂的 ReactJs 入门教程(精华版)
深刻浅出React(二):React开发神器Webpack