Author: 小灰灰
Last Update: 7th Apr, 2016
本系列将介绍如果完成一个react + redux的应用,本篇将介绍webpack以及react基础
Webpack
什么是webpack
?
// webpack is a module bundler
// This means webpack takes modules with dependencies
// and emits static assets representing those modules.
---- from http://webpack.github.io/
在项目中,webpack
负责编译js
,css
或者其他precompile-css语言,例如less
,sass
,通过不同的插件,你也可以配合babel
转换es6
,coffee
,jsx
等语言到浏览器可用的es5
.
Loaders
Loaders
在webpack
中起到重要的作用,他是一系列的应用到程序中各类资源文件上的转换器,例如你能够通过webapck
的配置以及参数设置、定义,告诉webpack
如何加载CoffeeScript
和jsx
.
两个重要的配置,如preLoaders
和loaders
:
{
preLoaders : [
{
test : / \.( js|jsx ) $/ ,
include : srcPath ,
loader : ' eslint-loader!macro-loader?config= ' + path . join ( __dirname , ' macros.json ' )
}
],
loaders : [
{
{
test : / \. sass/ ,
loader : ExtractTextPlugin . extract ( ' style-loader ' , ' css-loader!sass-loader?outputStyle=expanded&indentedSyntax ' )
},
{
test : / \. scss/ ,
loader : ExtractTextPlugin . extract ( ' style-loader ' , ' css-loader!sass-loader?outputStyle=expanded ' )
},
}
]
}
preLoaders
主要可以进行语法校验,以及其他自定义的预处理
loaders
主要是进行资源编译转换.
Plugins
通过插件机制,webpack
可以在不同的生命周期做更多的自定义行为.举几个例子:
HotModuleReplacementPlugin
热加载
DefinePlugin
注射环境变量到程序中,用于在不同的环境执行不同的逻辑
ExtractTextPlugin
用于分离样式文件
在实际开发中,往往需要根据开发环境,来决定程序中的行为,如在开发环境中模拟数据(mock),开启Debug信息等,在生产环境,使用ajax
都可以通过注入环境变量来实现。
// cfg/dev.js
{
plugins : [
new webpack . DefinePlugin ({
' process.env.NODE_ENV ' : " 'development' " ,
' process.env.MOCK_DATA ' : process . env . PREFER_MOCK_DATA
});
]
}
// api.js
// 在dev环境使用json mock数据,在其他环境使用api请求形式
if ( process . env . NODE_ENV === ' development ' && process . env . MOCK_DATA ) {
setTimeout (() => callback ( _products ), timeout || TIMEOUT )
} else {
fetch ( ` ${ API_BASEPATH } /question?limit= ${ QUESTIONS_LIMIT } ` )
}
HMR
“Hot Module Replacement” (HMR) is a feature to inject updated modules into the active runtime.
代码示例如下,我们在部分代码文件上开启热加载:
if ( module . hot ) {
// Enable Webpack hot module replacement for reducers
module . hot . accept ( ' ../reducers ' , () => {
const nextReducer = require ( ' ../reducers ' )
store . replaceReducer ( nextReducer )
});
}
Dev server
/*eslint no-console:0 */
' use strict ' ;
require ( ' core-js/fn/object/assign ' );
const webpack = require ( ' webpack ' );
const WebpackDevServer = require ( ' webpack-dev-server ' );
const config = require ( ' ./webpack.config ' );
const open = require ( ' open ' );
new WebpackDevServer ( webpack ( config ), config . devServer )
. listen ( config . port , ' localhost ' , ( err ) => {
if ( err ) {
console . log ( err );
}
console . log ( ' Listening at localhost: ' + config . port );
console . log ( ' Opening your system browser... ' );
open ( ' http://localhost: ' + config . port + ' /webpack-dev-server/ ' );
});
webpack
提供了微型的开发服务器,但是功能也十分强大,包括热替换的实现,自动刷新,基于配置的反向代理等
Proxy
Webpack dev server 利用 node-http-proxy
提供可配置的代理功能,将请求转发到外部的后端服务器
一个包含反向代理功能的实际的例子如下:
devServer : {
contentBase : ' ./src/ ' ,
historyApiFallback : true ,
hot : true ,
port : defaultSettings . port ,
publicPath : defaultSettings . publicPath ,
noInfo : false ,
proxy : {
' /api/* ' : {
target : ' http://localhost:8080/chosen-api ' ,
secure : ' false '
}
}
}
其他的进阶使用,请仔细查阅文档.
React
Component 组件
组件的核心思想就是定义并且重用你的View
,所有的Component
都继承自React
的BaseComponent
,如
import React , { Component , PropTypes } from ' react '
import { render } from ' react-dom '
class SimpleComponent extends Component {
render () {
< div > HelloWorld < /div >
}
}
// 讲Component插入到指定DOM节点
render (
< SimpleComponent />
document . getElementById ( ' example ' )
);
其中render
方法是核心要素,负责渲染(注:要注意的是render
并不会真正在浏览器中插入DOM元素,而是在内存中渲染一个虚拟的DOM,再通过update插入到浏览器界面中)
props
属性和state
状态
每个Component
组件有props
属性和state
状态
从下面的例子可以看到,props
属性是从外部或其他组件传入的。(注:仅仅从react
的角度来看,属性一般是不变的,或者推荐如此,如果不理解,就跳过这句话)
import React , { Component , PropTypes } from ' react '
class SimpleComponent extends Component {
render () {
return < div > this . props . content < /div >
}
}
class SimpleContainer extends Component {
render () {
const content = " helloworld from props " ;
return (
< div >
< SimpleComponent content = { content } / >
< /div >
);
}
}
而在react
中,state
是经常变化的属性,一般是由Component
自行维护,getInitialState
是一个生命周期提供的方法,可以提供state
的初始值.通过点击事件绑定,改变state
,会触发react
重新执行渲染动作.
值得重点关注的是,state
并不是直接通过改变key,value来进行update的,而是通过setState方法进行更新,这是因为state是一个不可变对象(immutable object),在许多现代编程语言中,都提供了Immutable对象,比如scala
,swift
等
在这里引入Immutable对象的好处是,使得程序的开发变得更加简单,程序员不用担心由于自身失误而导致错误的修改程序的状态,任何对状态的改变通过api进行,也可以有利于程序架构的改进,比如进行AOP
等等。不可变对象的更新不会在原有对象是进行改变,而总是会创建一个新的对象,你也许会对性能提出质疑,所以facebook
提供的immutablejs
通过structural sharing
来解决性能问题
引用facebook
对immutablejs
的介绍:
Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.
import React , { Component , PropTypes } from ' react '
class SimpleComponent extends Component {
getInitialState () {
return { liked : false };
}
handleClick () {
this . setState ({ liked : ! this . state . liked });
}
render () {
var text = this . state . liked ? ' like ' : ' haven \' t liked ' ;
return (
< p onClick = { this . handleClick } >
You { text } this . Click to toggle .
< /p >
);
}
}
propTypes “强类型”
使用propTypes
能够让react
在运行时帮助你检查代码,也有利于让其他开发者读懂你的组件
QuestionItem . propTypes = {
question : PropTypes . shape ({
id : PropTypes . number . isRequired ,
content : PropTypes . string . isRequired ,
choices : PropTypes . array . isRequired
}). isRequired ,
answer : PropTypes . number . isRequired ,
makeChoice : PropTypes . func . isRequired
}
Lifecycle 组件的生命周期
除了之前提到的getInitialState
辅助方法以外,react
的component
还为开发者提供更为精确的组件生命周期控制
Mouting: componentWillMount
Mouting: componentDidMount
Updating: componentWillReceiveProps
Updating: shouldComponentUpdate
Updating: componentWillUpdate
Updating: componentDidUpdate
Unmounting: componentWillUnmount
如果你从事过iOS
或者Android
开发,是不是发现和其他平台提供的Controller
/Activity/Fragment
的生命周期相仿
举个例子,在初次渲染完成后为代码块添加高亮:
import React , { Component , PropTypes } from ' react '
class QuestionItem extends Component {
highlightCodeBlocks () {
if ( this . props . question . content . indexOf ( ' ``` ' ) === - 1 ) {
return
}
var els = findDOMNode ( this . refs . QuestionContent ). querySelectorAll ( ' pre code ' )
for ( var i = 0 ; i < els . length ; i ++ ) {
hljs . highlightBlock ( els [ i ])
}
}
componentDidMount () {
this . highlightCodeBlocks ();
}
render () {
return < ReactMarkdown ref = "QuestionContent" source = { question . content } />
}
}
这里的ref
是为DOM
添加一个引用,方便在组件中通过ref
或者findDOMNode
得到该DOM
更多的细节与请参阅文档和社区