一、初入React世界
1.2 JSX语法
class 属性修改为className
for 属性修改为 htmFor
展开属性
使用ES6 rest/spread 特性提高效率
const data = { name: 'foo', value : 'bar' };
// const component = <Component name={data.name} value={data.value} />;
const component = <Component {...data}>
- 自定义HTML 属性
如果要使用HTML自定义属性,要使用data- 前缀
<div data-attr="xxx">content</div>
- HTML 转义
dangerouslySetInnerHTML 属性
<div dangerouslySetInnerHTML={{__html: 'cc ©:2015'}}></div>
1.3 React 组件
- props
- classPrefix: class前缀。对于组件来说,定义一个统一的class前缀,对样式与交互分离起了非常重要的作用。
- 用funtion prop 与父组件通信
- propTypes
用于规范 props 的类型与必需的状态
1.5 React 生命周期
React生命周期分成两类:
- 当组件在挂载或卸载时
- 当组件接收新的数据时,即组件更新时
推荐初始化组件
import React, { Component, PropTypes } from 'react';
class App extends Component {
// 类型检查
static propTypes = {
// ...
};
// 默认类型
static defaultProps = {
// ...
};
constructor(props) {
super(props);
this.state = {
// ...
};
}
componentWillMount() {
// ...
}
// 在其中使用setState 会更新组件
componentDidMount() {
// ...
}
render() {
return <div>This is a demo.</div>
}
}
componentWillUnmount 常常会执行一些清理方法
- 数据更新过程
如果自身的state更新了,那么会依次执行shouldComponentUpdate、componentWillUpdate 、render 和 componentDidUpdate。
如果组件是由父组件更新 props 而更新的,那么在 shouldComponentUpdate 之前会先执行componentWillReceiveProps 方法。
1.6 React与DOM
1.6.1 ReactDOM
其API非常少,只有findDOMNode,unmountComponentAtNode和render
- findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
componentDidMount() {
// this 为当前组件的实例
const dom = ReactDOM.findDOMNode(this);
}
render() {}
}
findDOMNode只对已经挂载的组件有效
DOM 真正被添加到 HTML 中的生命周期方法是componentDidMount 和 componentDidUpdate 方法
- render
把 React 渲染的Virtual DOM 渲染到浏览器的 DOM 当中,就要使用 render 方法
React 还提供了一个很少使用的 unmountComponentAtNode 方法来进行
卸载操作。
- refs
refs即reference,组件被调用时会新建一个该组件的实例,而refs就会指向这个实例。
二、漫谈React
2.1 事件系统
1、事件委派
React没有把事件直接绑定在真实的节点上,而是绑定在最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。
React中使用DOM原生事件时,要在组件卸载时手动一处,否则很可能出现内存泄漏的问题。
2.2 表单
3.select组件
单选和多选两种。在JSX语法中,可以通过设置select标签的 multiple={true}
来实现一个多选下拉列表。
2.2.2 受控组件
每当表单的状态发生变化时,都会被写入到组件的state中,这种组件在React中被称为受控组件(controlled component)。
受控组件更新state的流程:
(1)可以通过在初始 state 中设置表单的默认值
(2)每当表单的值发生变化时,调用onChange事件处理器
(3)事件处理器通过合成事件对象e拿到改变后的状态,并更新state
(4)setState触发视图的重新渲染,完成表单组件值得更新
2.2.3 非受控组件
如果一个表单组件没有 value props(单选按钮和复选框对应的是 checked prop)时,就可以称为非受控组件。相应地,也可以使用 defaultValue 和 defaultChecked prop来表示组件的默认状态。通常,需要通过为其添加ref prop来访问渲染后的底层DOM元素。
2.3 样式处理
2.3.3 CSS Modules
CSS Modules 内部通过ICSS来解决样式导入和导出两个问题,分别对应 :import 和 :export 两个新增的伪类
:import("path/to/dep.css") {
localAlias: keyFromDep;
/*...*/
}
:export {
exporteKey: exportedValue;
/*...*/
}
启用 CSS Modules
// webpack.config.js
css?modules&localIdentName=[name]_[local]-[hash:base64:5]
加上 modules 即为启用,其中 localIdentName 是设置生成样式的命名规则
使用webpack可以让全局样式和CSS Modules的局部样式和谐共存
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel',
}, {
test: /\.scss$/,
exclude: path.resolve(__dirname, 'src/styles'),
loader: 'style!css?modules&localIdentName=[name]_[local]!sass?sourceMap=true',
},{
test: /\.scsss$/,
include: path.resolve(__dirname,'src/styles'),
loader: 'style!css!sass?sourceMap=true',
}]
}
2.4 组件间通信
- 父组件通过props向子组件传递需要的信息。
- 子组件向父组件通信
- 利用回调函数
- 利用自定义事件机制
- 跨级组件通信
React中,我们可以使用 context 来实现跨级父子组件间的通信
// listItem组件
class ListItem extends Component {
static contextTypes = {
color: PropTypes.string,
};
render() {
const { value } = this.props;
return (
<li style={{background: this.context.color}}>{value}</li>
)
}
}
// List组件
class List extends Component {
static childContextTypes = {
color: PropTypes.string,
};
getChildContext() {
return {
color: 'red'
}
}
}
父组件中定义了 ChildContext
,这样从这一层开始的子组件都可以拿到定义的context。
2.5.2 高阶组件
实现高阶组件的方法有如下两种
- 属性代理(props proxy)。高阶组件通过被包裹的React组件来操作 props
- 反向继承(inheritance inversion)。高阶组件继承于被包裹的React组件
- 属性代理
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return <WrappedComponent {...this.props}>
}
}
}
当然我们也可以用 decorator 来转换
import React, { Component } from 'react';
@MyContainer
class MyComponent extends Component {
render();
}
export default MyComponent;
简单地替换成作用在类上的decorator,即接受需要装饰的类为参数。
- 控制 props
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
const newProps = {
text: newText,
};
return <WrappedComponent {...this.props} {...newProps}>
}
}
}
- 通过 refs 使用引用
高阶组价中,我们可以接受 refs 使用 WrappedComponent 的引用。
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method();
}
render() {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this),
});
return <WrappedComponent {...props}>
}
}
}
- 抽象 state
抽象一个input组件
import React, { Componet } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
constructor(props) {
super(props);
this.state = {
name:'',
}
this.onNameChange = this.onNameChange.bind(this);
}
}
onNameChange(event) {
this.setState({
name: event.target.value,
});
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
}
}
return <WrappedComponent {...this.props} {...newProps} />
}
}
- 使用其他元素包裹 WrappedComponent
import React,{ Component } from 'react';
const MyContainer = (WrappedComponent) => {
class extends Component {
render() {
return (
<div style={{display: 'block'}}>
<WrappedComponent {...this.props}>
</div>
)
}
}
}
// 受控input组件使用
@MyContainer
class MyComponent extends Component {
render() {
return <input name="name" {...this.props.name}>
}
}
高阶组件和mixin的不同
- 反向继承
- 渲染劫持
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
if(this.props.loggedIn) {
return super.render();
} else {
return null;
}
}
}
}
// 实例二:对render结果进行修改
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if(elementsTree && elementsTree.type === 'input'){
newProps = {value: 'May the force be with you'};
}
const props = Object.assign({}, elementsTree.props, newProps);
const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.childre);
return newElementsTree;
}
}
}
- 控制state
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
return (
<div>
<h2>HOC Debugger Component</h2>
<p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</div>
)
}
}
}
- 组件命名
- 组件参数
import React, { Component } from 'react';
function HOCFactory(...params) {
return function HOCFactory(WrappedComponent) {
return class HOC extends Component {
render() {
return <WrappedComponent {...this.props}>
}
}
}
}
// 使用
HOCFactoryFactory(params)(WrappedComponent);
// 或者
@HOCFactoryFactory(params);
class WrappedComponent extends React.Component{}
2.6 组件性能优化
- 纯函数
- 给定相同的输入,它总能返回相同的输出
- 过程没有副作用
- 没有额外的状态依赖
PureRender
为重新实现了 shouldComponentUpdate 生命周期方法,让当前传入的 props
和 state 与之前的作浅比较,如果返回 false,那么组件就不会执行 render 方法。react-addons-perf
量化所做的性能优化效果
Perf.start()
Perf.stop()
2.7 动画
TransitionGroup 能帮助我们快捷地识别出增加或删除的组件。
React Transition设计了以生命周期函数的方式来实现,即让子组件的每一个实例都实现相应地生命周期函数。当React Transition识别到某个子组件增或删时,则调用它相应地生命周期函数。我们可以再生命周期函数中实现动画逻辑。
如果每一个子组件的动效相同,那么每一个子组件可以共同用一个生命周期函数。因此React Transition 提供了 childFactory 配置,让用户自定义一个封装子组件的工厂方法,为子组件加上相应地生命周期函数。
React Transition提供的生命周期
- componentWillAppear
- componentDidAppear
- componentWillEnter
- componentDidEnter
- componentWillLeave
- componentDidLeave
componentWillxxx 只要在 componentWillReceiveProps
中对this.props.children
和nextProps.children
做一个比较就可以了。componentDidxxx
可以在componentWillxxx
提供一个回调函数,用来执行componentDidxxx
React CSS Transition 为子组件的每个生命周期加了不同的className,这样用户可以很方便地根据 className 地变化来实现动画
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={400}
>
{items}
</ReactCSSTransitionGroup>
对应地css代码
.example-enter {
transform: scaleY(0);
&.example-enter-active {
transform: scaleY(1);
transition: transform .4s ease;
}
}
使用react-motion实现一个spring开关
import React, {Component} from ''react;
class Switch extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
open: false,
}
}
handleClick() {
this.setState({
open: !this.state.open
})
}
render() {
return (
<Motion style={{x: spring(this.state.open ? 400 : 0)}}>
{({x}) =>
<div className="demo">
<div
className="demo-block"
onClick={this.handleClick}
style={{
transform: `translate3d(${x}px, 0, 0)`,
}}
/>
</div>
}
</Motion>
)
}
}
深入Redux 应用框架
5.1 Redux简介
5.1.2Redux三大原则
- 单一数据源
- 状态是只读地
- 状态修改均由纯函数完成
5.1.3 Redux 核心API
Redux核心是一个store,这个store是由createStore(reducers[,initialState])方法生成
通过createStore
方法创建的store是一个对象,包含4个方法
- getState(): 获取store中当前的状态
- dispatch(action):分发一个action,并返回这个action,这是唯一能改变store中数据的方式
- subscribe(listener): 注册一个监听者,它在store发生改变时被调用
- replaceReducer(nextReducer): 更新当前store里的reducer,一般只会在开发模式中调用该方法。
5.1.4 与React 绑定
需要使用react-redux
进行react和redux的绑定,其提供了一个组件和API帮助Redux和React进行绑定,一个是 React组件<Provider />,一个是 connect(),<Provider />接受一个 store 作为props,它是整个Redux应用的顶层组件,而connect()
提供了在整个React应用的任意组件中获取store中数据的功能。
5.2 Redux middleware
Redux 提供了 applyMiddleware 方法来加载 middleware,其源码如下
import compose from './compose';
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
// 获取得到原始的store
let store = next(reducer, initialState);
let dispatch = store.dispatch;
// 赋值一个空数组,用来存储后新的dispatch分裂函数
let chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
// 将分裂的函数组合每次都会执行,即每次都执行这些中间件函数
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispath
}
}
}
middleware运行原理
- 函数式编程思想设计
通过函数式编程中的 currying。currying 的middleware结构的好处有以下两点
- 易串联:不断currying形成的middleware可以累积参数,再配合组合方式,很容易形成 pipeline来处理数据流
- 共享store:applyMiddleware执行过程中,store还是旧的,applyMiddleware完成后,所有的middleware内部拿到的sotore都是最新且相同的
给middleware分发store
let newStore = applyMiddleware(mid1, mid2, mid3)(createStore)(reducer, null);
组合串联middleware
dispatch = compose(...chain)(store.dispatch)
Redux中compose的实现
function compose(...funs) {
return arg => funs.reduceRight((composed, f) => f((composed), arg))
}
compose(...funcs) 返回的是一个匿名函数,其中 funcs 就是 chain 数组。当调用 reduceRight
时,依次从 funcs 数组的右端取一个函数 fx 拿来执行,fx 的参数 composed 就是前一次 fx+1 执
行的结果,而第一次执行的 fn(n 代表 chain 的长度)的参数 arg 就是 store.dispatch。
- 在 middleware 中调用 dispatch 会发生什么
如果这个middleware粗暴的调用 store.dispatch(acton),就会形成无线循环了。
这里我们就用到了Redux Thunk。
Redux Thunk 会判断 action 是否是函数。如果是,则执行 action,否则继续传递 action 到下一个 middleware。
const tuhun = store => next => action => {
typeof action === 'function' ?
action(store.dispatch, store.getState) :
next(action)
}
5.3 Redux 异步流
5.3.1 使用 middleware 简化异步请求
- redux-thunk
我们再来看看 redux-thunk 的源代码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
模拟请求天气的异步请求,action的写法
function getWeather(url, params) {
return (dispatch, action) {
fetch(url, params)
.then(result => {
dispatch({
type: 'GET_WEATHER_SUCCESS',
payload: result,
})
})
.catch(err => {
dispatch({
type: 'GET_WEATHER_ERROR',
payload: err,
})
})
}
}
- redux-promise
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}
我们利用 ES7 的 async 和 await 语法,可以简化上述异步过程:
const fetchData = (url, params) => fetch(url, params);
async function getWeather(url, params) {
const result = await fetchData(url, params);
if (result.error) {
220 第 5 章 深入 Redux 应用架构
return {
type: 'GET_WEATHER_ERROR',
error: result.error,
};
}
return {
type: 'GET_WEATHER_SUCCESS',
payload: result,
};
}
- redux-saga
在 Redux 社区,还有一个处理异步流的后起之秀,名为 redux-saga。它与上述方法最直观的
不同就是用 generator 替代了 promise,我们通过 Babel 可以很方便地支持 generator.
Redux 与 路由
我们可以通过 <Router> 、<Route> 这两个标签以及一系列属性
定义整个 React 应用的路由方案。
前端开发热加载,安装 webpack-dev-server
npm install -D webpack-dev-server
./node_modules/.bin/webpack-dev-server --hot --inline --content-base
在 mapStateToProps 中,我们从整棵 Redux 状态树中选取了 state.home.list 分支作为当前
组件的 props,并将其命名为 list。这样,在 Home 组件中,就可以使用 this.props.list 来获取
到所有 PreviewListRedux 中定义的状态。
而在 mapDispatchToProps 中,我们从前面提到的 HomeRedux.js 中引入了 listActions,并使
用 Redux 提供的工具函数将 listActions 中的每一个 action creator(目前只有一个)与 dispatch 进
行绑定,最终我们可以在 Home 组件中使用 this.props.listActions 来获取到绑定之后的 action
creator。