《深入React技术栈》笔记

一、初入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 &copy:2015'}}></div>

1.3 React 组件

  • props
  1. 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 方法。

React生命周期整体流程图

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 组件间通信

  1. 父组件通过props向子组件传递需要的信息。
  2. 子组件向父组件通信
  • 利用回调函数
  • 利用自定义事件机制
  1. 跨级组件通信
    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组件
  1. 属性代理
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的不同

mixin与高阶组件的区别.png
  1. 反向继承
  • 渲染劫持
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>
      )
    }
  }
}
  1. 组件命名
  2. 组件参数
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 组件性能优化

  1. 纯函数
  • 给定相同的输入,它总能返回相同的输出
  • 过程没有副作用
  • 没有额外的状态依赖
  1. PureRender
    为重新实现了 shouldComponentUpdate 生命周期方法,让当前传入的 props
    和 state 与之前的作浅比较,如果返回 false,那么组件就不会执行 render 方法。

  2. 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.childrennextProps.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三大原则

  1. 单一数据源
  2. 状态是只读地
  3. 状态修改均由纯函数完成

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运行原理

  1. 函数式编程思想设计
    通过函数式编程中的 currying。currying 的middleware结构的好处有以下两点
  • 易串联:不断currying形成的middleware可以累积参数,再配合组合方式,很容易形成 pipeline来处理数据流
  • 共享store:applyMiddleware执行过程中,store还是旧的,applyMiddleware完成后,所有的middleware内部拿到的sotore都是最新且相同的
  1. 给middleware分发store
    let newStore = applyMiddleware(mid1, mid2, mid3)(createStore)(reducer, null);

  2. 组合串联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。

  1. 在 middleware 中调用 dispatch 会发生什么
Redux middleware流程图.png

如果这个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 简化异步请求

  1. 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,
        })
      })
  }
}
  1. 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,
 };
} 
  1. 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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • 做React需要会什么? react的功能其实很单一,主要负责渲染的功能,现有的框架,比如angular是一个大而...
    苍都阅读 14,740评论 1 139
  • 学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...
    贺贺v5阅读 8,877评论 10 58
  • 有槽先吐 花了几天时间,大致读完了《深入React技术栈》,简单总结的话,不及预期。 作者成书前,在知乎开设pur...
    ronniegong阅读 4,958评论 1 8
  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,894评论 23 111
  • 一个吃过午饭犯困的午后,加上感冒药的作用,没精神。 昨天得知要去外地培训的事情,打破了我的计划。设想了很多的后果,...
    熊猫ii阅读 309评论 0 0