React核心内容归纳总结 【*】

状态、属性、组件API、组件的生命周期

当react的状态改变时,自动执行this.render()方法更新组件
ES6写React的时候,事件里不会自动绑定this,需要自己绑定,或者直接在constructor里写方法

constructor(props) {
    super(props);
    this.state = {
        liked: false
    };
    this.handleClick = (e) => {
        this.setState({liked: !this.state.liked});
    };
}

 

状态

import React from 'react';

class LikeButton extends React.Component {
    // es6用constructor代替getInitialState
    constructor(props) {
        super(props);
        this.state = {
            liked: false
        }
    }
    handleClick (e) {
        this.setState({liked: !this.state.liked})
    }
    render () {
        let text = this.state.liked ? '喜欢': '不喜欢';

        return (
            <div>
                <p onClick={this.handleClick.bind(this)}>
                    你喜欢红茶吗?{text}
                </p>
            </div>
        )
    }
}

export default LikeButton;

 

props
HelloMessage.jsx

import React from 'react';

class HelloMessage extends React.Component {
    render () {
        return (
            <div>
                Hello, {this.props.name}
            </div>
        )
    }
}

export default HelloMessage;

main.js

import React from 'react';
import ReactDOM from 'react-dom';

import HelloMessage from './component/HelloMessage';

const app = document.getElementById('app');

ReactDOM.render(<HelloMessage name="Runoob"/>, app);

 

props验证
可以保证应用组件被正确使用
React.PropTypes提供很多验证器(validator)来验证传入数据是否有效。当props传入无效数据时,JavaScript控制台会抛出错误。

import React from 'react';

class HelloMessage extends React.Component {
    render () {
        return (
            <div>
                Hello, {this.props.name}
            </div>
        )
    }
}

// getDefaultProps要写在外部
HelloMessage.defaultProps = {
    name: 'this is default name'
};

HelloMessage.propTypes = {
    name: React.PropTypes.string.isRequired
};

export default HelloMessage;

 

更多验证器

React.createClass({
  propTypes: {
    // 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
   optionalArray: React.PropTypes.array,
    optionalBool: React.PropTypes.bool,
    optionalFunc: React.PropTypes.func,
    optionalNumber: React.PropTypes.number,
    optionalObject: React.PropTypes.object,
    optionalString: React.PropTypes.string,

    // 可以被渲染的对象 numbers, strings, elements 或 array
    optionalNode: React.PropTypes.node,

    //  React 元素
    optionalElement: React.PropTypes.element,

    // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
    optionalMessage: React.PropTypes.instanceOf(Message),

    // 用 enum 来限制 prop 只接受指定的值。
    optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),

    // 可以是多个对象类型中的一个
    optionalUnion: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number,
      React.PropTypes.instanceOf(Message)
    ]),

    // 指定类型组成的数组
    optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),

    // 指定类型的属性构成的对象
    optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),

    // 特定 shape 参数的对象
    optionalObjectWithShape: React.PropTypes.shape({
      color: React.PropTypes.string,
      fontSize: React.PropTypes.number
    }),

    // 任意类型加上 `isRequired` 来使 prop 不可空。
    requiredFunc: React.PropTypes.func.isRequired,

    // 不可空的任意类型
    requiredAny: React.PropTypes.any.isRequired,

    // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
    customProp: function(props, propName, componentName) {
      if (!/matchme/.test(props[propName])) {
        return new Error('Validation failed!');
      }
    }
  },
  /* ... */
});

 

React组件API
setState 设置状态
replaceState 替换状态
setProps 设置属性
replaceProps 替换属性
forceUpdate 强制更新
findDOMNode 获取DOM节点,查找真实DOM

React.findDOMNode()只在mounted组件中调用,如果在组件的render()方法中调用React.findDOMNode()就会抛出异常。


React组件的生命周期
组件生命周期的三个状态
Mounting: 已插入真实DOM
Updating: 正在被重新渲染
Unmounting: 已移除真实DOM

生命周期方法
componentWillMount 在渲染前调用,在客户端也在服务端
componentDidMount 在第一次渲染后调用,只在客户端。
之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。
如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
componentWillReceiveProps 在初始化render时不会被调用,当组件接收到一个新的prop时被调用
shouldComponentUpdate 返回一个布尔值。当确认不需要更新组件时使用。在组件接收到新的state或新的props时被调用。在初始化时或使用forceUpdate时不被调用
componentWillUpdate 初始化不会调用。组件接收到新的state或者props但还没有render时被调用
componentDidUpdate 初始化不会调用。组件完成更新后立即调用。
componentWillUnmount 组件从DOM中移除的时候立刻被调用。

用state判断是否已经插入真实DOM

componentWillMount() {
    this.mounted = false;
}
componentDidMount() {
    this.mounted = true;
}

 

需求:写一个实例,在组件加载以后,通过componentDidMount设置一个定时器,改变组件的透明度并重新渲染
HelloMessage.jsx

import React from 'react';

class HelloMessage extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            opacity: 1.0
        };
    }
    componentDidMount () {
        this.timer = setInterval(() => {
            let opacity = this.state.opacity;
            opacity -= 0.05;
            if(opacity <= 0.1) {
                opacity = 1.0;
            }
            this.setState({
                opacity: opacity
            });
        }, 100);
    }
    render () {
        let myStyle = {
            fontFamily: 'microsoft yahei',
            width: '100%',
            height: '100px',
            background: 'red',
            opacity: this.state.opacity
        };

        return (
            <div style={myStyle}>
                Hello, {this.props.name}
                <p>{this.state.opacity}</p>
            </div>
        )
    }
}

// getDefaultProps要写在外部
HelloMessage.defaultProps = {
    name: 'this is default name'
};

HelloMessage.propTypes = {
    name: React.PropTypes.string.isRequired
};

export default HelloMessage;

 

另一个实例,体现react的生命周期

import React from 'react';

class Content extends React.Component {
    componentWillMount () {
        console.log('组件将被插入到真实DOM');
    }
    componentDidMount () {
        console.log('已经插入真实DOM');
    }
    componentWillReceiveProps (newProps) {
        console.log('组件将要接收属性,新属性是:');
        console.log(newProps);
    }
    shouldComponentUpdate (newProps, newState) {
        return true;
    }
    componentWillUpdate (nextProps, nextState) {
        console.log('组件将要更新,下一个属性:' + nextProps + ',下一个状态:' + nextState);
        console.log(nextProps);
    }
    componentDidUpdate (prevProps, prevState) {
        console.log('组件已更新,上一个属性:' + prevProps + ',上一个状态:' + prevState);
        console.log(prevProps);
    }
    componentWillUnmount () {
        console.log('已经移除真实DOM');
    }
    render () {
        return (
            <div>
                次数:{this.props.myNum}
            </div>
        )
    }
}

class Increment extends React.Component {
    constructor (props) {
        super(props);
        this.state = {
            data: 0
        };
    }
    setNewNumber () {
        this.setState({
            data: this.state.data + 1
        });
    }
    render () {
        return (
            <div>
                <button onClick={this.setNewNumber.bind(this)}>Increment</button>
                <Content myNum={this.state.data}/>
            </div>
        )
    }
}

export default Increment;

 

 

 

 

 

 

 

 

https://github.com/dzfrontend/start-with-react.js

 

1.React 环境配置与调试技巧

1-1.使用npm配置react环境

安装react环境:

下面安装react开发所必须的插件,也可以直接在package.json所在的目录里面直接npm install安装所有的插件。

初始化:npm init  

安装:npm install react react-dom babelify babel-preset-react babel-preset-es2015 --save

1-2.webpack热加载配置

安装webpack和webpack-dev-server:

npm install webpack -g 和--save-dev
npm install webpack-dev-server --save-dev

webpack.config.js

webpack.config.js里面为webpack打包的配置,最后生成打包文件bundle.js:

var webpack = require('webpack');
var path = require('path');
module.exports = {
  context: path.join(__dirname),
  entry: "./src/js/index.js",
  module: {
    loaders: [
      {
        test: /\.js?$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        query: {
          presets: ['react', 'es2015']
        }
      }
    ]
  },
  output: {
    path: __dirname,
    filename: "./src/bundle.js"
  }
};  

这里配置的webpack.config.js,命令行输入webpack-dev-server,将入口文件src/js/index.js生成打包文件/src/bundle.js。

所以在index.html里面只需要引入打包好的bundle.js文件就可以访问整个项目。访问http://localhost:8080/webpack-dev-server/就可以访问根目录index.html并且实现热加载自动刷新浏览器;

1-3.调试技巧- Chrome React插件的使用

谷歌应用商店搜索React Developer Tools安装

1-4.开发工具Atom介绍

atom开发相关插件

emmet 代码补全  

atom-ternjs 对es5 es6 nodejs等语法支持

atom-beautify 对html css js等格式化代码

open-in-browser 打开浏览器

file-icons 文件图标

highlight-line 高亮行,光标定位

highlight-selected 高亮选择,选择相同的内容  

2.React 组件基础

2-1.React组件

组件写在js文件里面

React导入:

在React组件定义之前需要导入
  import React from 'react';
  import ReactDOM from 'react-dom';

组件定义:

class + 组件名 + extends React.Component{
  render(){
    return(jsx代码)
  }
}

规范:组件名大写

组件导出:

在React组件定义之后需要导出
export default

组件导入:

其他页面使用该组件用import导入
如import ComponentHeader from './components/header';

入口定义:

想要将组件显示到页面中,还需要定义入口:
ReactDOM.render('组件名',document.getElementById('id'));

2-2.React多组件嵌套

将三个组件< ComponentHeader />,< ComponentBody />和< ComponentFooter />写在一起,然后放到指定的入口最终显示到页面中

// 多组件嵌套
class Index extends React.Component{
  render(){
    return(
      <div>
        <ComponentHeader/>
        <ComponentBody/>
        <ComponentFooter/>
      </div>
    )
  }
}
// 入口定义:
ReactDOM.render(
  <Index/>,
  document.getElementById('example')
);

2-3.JSX内置表达式

三元表达式:

绑定属性:

属性:{变量} 绑定变量不需要引号

JSX注释:

代码

class ComponentBody extends React.Component{
  render(){
  	var username = 'dz';
  	var boolInput = true;
    return(
      <div>
        <h1>这里是页面主体内容</h1>
        {/*三元表达式*/}
        <p>{username === '' ? '用户还未登录' : '登录用户名:' + username}</p>
    	{/*绑定属性*/}
        <p><input type="submit" value="提交" disabled={boolInput} /></p>
      </div>
    )
  }
}

2-4.React生命周期

生命周期函数图解:

3.React 属性与事件

3-1.state属性

初始化state: 放在构造函数constructor里

constructor() {
  super(); //调用基类的所有的初始化方法
  this.state = {
    username: "Parry",
    age: 20
  }; //初始化赋值
}

修改state:
放在render函数里

this.setState({username:'db',age:19});

显示:
jsx里

{this.state.username} {this.state.age}

完整代码:

import React from 'react';
export default class ComponentBody extends React.Component{
  constructor(){
    super();//调用基类的所有的初始化方法
    //初始化state,需要放在构造函数constructor里面
    this.state = {
      username:'dz',
      age:20
    };
  }
  render(){
  	setTimeout(() => {
      //修改state
      this.setState({username:'db',age:19});
    },2000);
    return(
      <div>
        <h1>这里是页面主体内容</h1>
        <h1>state</h1>
        <p>{this.state.username} {this.state.age}</p>
      </div>
    )
  }
}

3-2.props属性

state对于模块属于自身属性,当一个组件想要接收参数的时候用props接收,props对于组件属于外来属性

1.组件传递参数

<ComponentBody userid={123456}/>

2.组件中接收参数

this.props.userid

3-3.事件与数据的双向绑定

事件的绑定:

首先在组件React.Component中写事件函数

changeUserInfo() {
  this.setState({age: 21});
};

然后调用时绑定

render(){
  return(
    <div>
      <p>事件绑定数据:{this.state.age}</p>
      {/*事件绑定,这里bind(this)实现继承*/}
      <input type="submit" value="改变1" onClick={this.changeUserInfo.bind(this)}/>
    </div>
  )
}

子页面向父页面传递参数的方法

在子页面中调用父页面传递过来的事件,用props接收事件函数,进行组件间的参数传递

在父组件中:

handleChildValueChange(event){
  //event.target.value接收表单输入的值
  this.setState({age: event.target.value});
}
render(){
  return(
    <div>
      <p>事件绑定数据:{this.state.age}</p>
      {/*子组件向父组件传递参数:需要父页面向子组件传递事件函数*/}
      <ComponentBodyChild handleChildValueChange={this.handleChildValueChange.bind(this)}/>
    </div>
  )
}

在子组件ComponentBodyChild中:

render(){
  return(
    <div>
      {/*子组件中用props接收事件函数*/}
      <p>子页面输入:<input type="text" onChange={this.props.handleChildValueChange}/></p>
    </div>
  )
}

3-4.可复用组件

PropTypes属性验证:

PropTypes 提供很多验证器 (validator) 来验证传入数据的有效性。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

import React from 'react';
import PropTypes from 'prop-types'; //npm install prop-types

export default class ComponentBody extends React.Component{
  render(){
    return(
      <div>
        <p>接收父组件参数为:{this.props.userid}</p>
      </div>
    )
  }
}

//PropTypes属性验证在类定义后追加属性propTypes
ComponentBody.propTypes = {
  userid : PropTypes.number //userid只能为number,否则会报错
};

默认prop值:

//比如userid或username在父页面没有传递值的时候,需要设置默认值
const defaultProps = {
  username:'这是一个默认用户名'
};
ComponentBody.defaultProps = defaultProps;

传递所有参数的快捷方式:

父页面向孙子页面传递多个参数的时候,在子页面用语法糖获取所有的参数

<Component {...this.props 其他属性="属性值"}

3-5.组件的Refs

组件的Refs:获取原生的html节点

import React from 'react';
import ReactDOM from 'react-dom';

export default class ComponentBody extends React.Component{
  changeUserInfo1(){
    //DOM操作第一种方式
    var btn = document.getElementById('idButton');
    console.log(btn);
    ReactDOM.findDOMNode(btn).style.color = 'red';
  };
  changeUserInfo2(){
    //DOM操作第二种方式
    console.log(this.refs.refButton); //获取原生dom节点
    this.refs.refButton.style.color = 'red';
  };
  render(){
    return(
      <div>
        <input id="idButton" type="button" value="DOM操作第一种方式" onClick={this.changeUserInfo1.bind(this)}/>
        <input ref="refButton" type="button" value="DOM操作第二种方式" onClick={this.changeUserInfo2.bind(this)}/>
      </div>
    )
  }
}

推荐的是使用组件的Refs进行dom操作。

Refs是访问到组件内部DOM节点唯一可靠的方法;
Refs会自动管理销毁对子组件的引用;
不要在render或render之前对Refs进行调用,因为组件还没加载好;
不要滥用Refs,能用state解决的用state

3-6.独立组件间共享Mixins

Mixins在组件间进行事件的共享:不同组件间共用功能、共享代码

Mixins用ES6写法需要安装插件:https://github.com/brigand/react-mixin;ES5写法看react官方文档,代码用的是ES6写法。

安装:

npm install --save react-mixin@2  

使用:

var reactMixin = require('react-mixin');
var someMixin = require('some-mixin');
class Foo extends React.Component {
    render: function(){ return <div /> }    
}
reactMixin(Foo.prototype, someMixin);
reactMixin(Foo.prototype, someOtherMixin);

看不懂上面的看demo,demo里面有。

4.React 样式

4-1.内联样式

内联样式:css需要用驼峰写法  

外部样式:index.html文件中全局引用外部css文件,class需要改成className  

内联样式中的表达式:三元表达式  

// React导入
import React from 'react';
// 组件定义和导出:
export default class ComponentHeader extends React.Component{ //继承React.Component则这个类就是一个组件
  constructor(){
    super();
    this.state = {
      miniHeader: true
    };
  };
  switchHeader(){
    this.setState({
      miniHeader: !this.state.miniHeader
    });
  };
  render(){
    const styleComponentHeader = {
      header:{
        backgroundColor:(this.state.miniHeader) ? '#c73949' : '#666',
        color:'#fff',
        paddingTop:(this.state.miniHeader) ? '15px' : '5px',
        paddingBottom:(this.state.miniHeader) ? '15px' : '5px'
      },
      //还可以定义其他的样式
    };
    return(
      <header style={styleComponentHeader.header} className="smallFontSize" onClick={this.switchHeader.bind(this)}>
        <h1>这里是头部组件 点我</h1>
      </header>
    )
  };
}

4-2.css模块化

安装style-loader、css-loader => css模块化
安装babel-plugin-react-html-attrs插件 => 可以直接写class不用改写className

在webpack.config.js的loaders里面加上css模块化的配置:

{
  test: /\.css$/,
  loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]'
}

css模块化:

import React from 'react';
//css模块化
var footerCss = require('../../css/footer.css');

export default class ComponentFooter extends React.Component{
  render(){
    console.log(footerCss);
    return (
      <footer class={footerCss.miniFooter}>
        <h1>这里是尾部组件</h1>
      </footer>
    )
  }
}

4-3.Ant Design样式框架

使用请看官方文档:https://ant.design/docs/react/introduce-cn

5.React Router

作用是在单页面中进行组件间的页面跳转。

github地址:https://github.com/ReactTraining/react-router

react-router现在到了4.x的版本,这里面使用的是3.0.5版本,有差异。

5.1.React Router

配置路由:

Rendering a Route:文档参考 https://github.com/reactjs/react-router-tutorial/tree/master/lessons/02-rendering-a-route

root.js作为页面入口文件,webpack-dev-server运行启动项目

import React from 'react';
import ReactDOM from 'react-dom';
// 导入路由组件
import Index from './index';
import ComponentList from './components/list';
//导入路由配置插件
import {Router,Route,hashHistory} from 'react-router';

//入口页面
export default class Root extends React.Component{
  render(){
    return (
      //这里替换了之前的 Index,变成了程序的入口
      // 用hashHistory实现路由,下面为路由配置:
      <Router history={hashHistory}>
        <Route component={Index} path="/"></Route>
        <Route component={ComponentList} path="list"></Route>
      </Router>
    );
  };
}
// 入口定义:
ReactDOM.render(<Root/>, document.getElementById('example'));

当访问根目录也就是访问http://localhost:8080/的时候,会访问Index组件,也就是< Route component={Index} path="/" >< /Route >这句话表示的意思;上面配置了两个路由http://localhost:8080/#/http://localhost:8080/#/list分别链接到两个组件页面。

路由链接:

Navigating with Link:文档参考 https://github.com/reactjs/react-router-tutorial/tree/master/lessons/03-navigating-with-link

在路由配置好了之后,在任意一个页面都可以写上路由链接:

某个子页面:

import React from 'react';
//导入link组件
import {Link} from 'react-router';

export default class ComponentHeader extends React.Component{
  render(){
    return(
      <div>
        <h1>这里是头部组件</h1>
        <ul>
          <li><Link to={`/`}>首页</Link></li>
          <li><Link to={`/list`}>list页面</Link></li>
        </ul>
      </div>
    )
  }
}

这样就可以访问配置的路由了。

路由嵌套:

组件Index路由嵌套一个组件ComponentDetails路由:

render(){
  return (
    <Router history={hashHistory}>
      <Route component={Index} path="/">
        <Route component={ComponentDetails} path="details"></Route>
      </Route>
    </Router>
  );
};  

嵌套后,还需要在Index组件中将嵌套路由展示出来

render(){
  return(
    <div>
      {/* 路由嵌套展示区域 */}
      <div>
		{this.props.children}
	  </div>
    </div>
  )
}

5.2.React Router参数传递

如果链接后面加上访问参数:

<Link to={`/list`}>list页面</Link>

加上访问参数1234
<Link to={`/list/1234`}>list页面</Link>

则路由需要配置为:

加一个/:id
<Route component={ComponentList} path="list/:id"></Route>

接收页面传递的参数为:

{this.props.params.id}

 

 

 

 

虚拟DOM内部是如何工作的?

流程图展现VDOM在Preact中如何工作

虚拟DOM (VDOM 也叫 VNode)非常有魔力 ✨ 但是也非常复杂和难以理解😱. React, 在Preact和一些类似的JS库的核心代码中使用. 不幸的是我发现没有一篇好的文章或者文档简洁明了的来介绍它。 因此我决定自己写一篇.

注意: 这篇文章很长. 我已经添加尽可能多的图片来使其理解更简单一些,但是我发现这样的话,文章更长了.

我用的是 Preact’s 代码 和 VDOM,因为它很小,你可以在将来很舒适的阅读它。 但是我相信几乎所有的概念同样适用于React.

我希望你读完这篇文章后,能够很容易的理解像React或者Preact的库,甚至对你写出类似的库也是有帮助的

在这篇博客中,我将会举一些简单示例,并且复习一下不同的小知识,给你一个关于它们到底如何工作的概念。特别地,我会复习:

  1. Babel 和 JSX

  2. 创建一个VNode - 一个简单的虚拟DOM元素

  3. 处理组件及子组件

  4. 初始化渲染并且创建一个DOM元素

  5. 重新渲染

  6. 移除DOM元素

  7. 替换DOM元素

关于这个demo:

这是一个简单过滤搜索应用, 仅包含有两个组件“FilteredList” 和 “List”。这个List组件渲染列表项(默认是“California” 和 “New York”)。这个应用有一个搜索的区域,可以根据字母来过滤列表项。非常的直观。

相关图片(点击放大,查看更多细节,原文在medium里是可以放大的,这里貌似不行)

应用代码: http://codepen.io/rajaraodv/pen/BQxmjj

大图

高级一点儿,我们用JSX写了组件,可以通过babel的命令行工具将其转换为原生的JS.然后Preact的“h”函数将它转换为虚拟DOM树(也称为 VNode)。最后Preact的虚拟DOM算法,根据虚拟DOM创建一个真实的DOM,来构成我们的应用。

大图

在我们深入理解VDOM的生命周期之前,让我们理解下JSX,它为库提供了基础

1. Babel 和 JSX

在React中,像Preact这样的库,没有HTML语法,取而代之的是一切皆Javascript。因此我们需要用Javascript来写HTML。但是用原生JS写DOM是一种噩梦。 😱

对于我们的应用,我们将会像下面这样书写HTML:

注意: 等会儿我会介绍“h”

这就是JSX的由来,JSX本质上允许我们在Javascript中书写HTML!并且允许我们在HTML中的{}号中使用JS的语法。

JSX帮助我们像下面这样很容易的书写组件:

将JSX树转换为Javascript

JSX很酷,但是不是合法的JS,但是根本上我们还是需要真实的DOM。JSX仅仅是帮助我们书写真实DOM的一种方法。除此之外,它毫无用处。

因此我们需要一种方法将JSX转换为正确的JSON对象(VDOM 也是一个“树”形的结构),我们需要将JSX作为创建真实DOM的基础。我们函数来做这样的事情.

在Preact中这个函数就是“h”函数.它作用和React中的React.createElement作用是一样的。

“h”是指 hyperscript - 一种可以通过JS来创建HTML的库。

但是怎样将JSX转换为“h”函数式的调用?这就是Babel的由来。Babel可以很轻松的遍历JSX的节点,然后将它们转换为“h”函数式的调用。

Babel JSX (React Vs Preact)

在React中babel会将JSX转换为React.createElement函数调用

左边: JSX 右边: React 的JS版本 (点击放大)

我们可以像下面这样增加[Babel Pragma]配置,可以很轻松为Preact的函数的名字起任何一个你想起的名字。

Option 1:
//.babelrc
{   "plugins": [
      ["transform-react-jsx", { "pragma": "h" }]
     ] 
}
Option 2:
//Add the below comment as the 1st line in every JSX file
/** @jsx h */

“h” —通过Babel的配置 (点击放大)

挂载到真实DOM

组件的的render方法中的代码不仅被转换为“h”函数,而且开始挂载。

这是执行和一切的开始

//Mount to real DOM
render(<FilteredList/>, document.getElementById(‘app’));
//Converted to "h":
render(**h(FilteredList)**, document.getElementById(‘app’));

“h”函数的输出

The “h” function takes the output of JSX and creates something called a “VNode” (React’s “createElement” creates ReactElement). A Preact’s “VNode” (or a React’s “Element”) is simply a JS object representation of a single DOM node with it’s properties and children.

看起来像下面这样:

{
   "nodeName": "",
   "attributes": {},
   "children": []
}

举个例子,我们的应用的Input表单的VNode像这样:

{
   "nodeName": "input",
   "attributes": {
    "type": "text",
    "placeholder": "Search",
    "onChange": ""
   },
   "children": []
  }

Note: “h” function doesn’t create the entire tree! It simply creates JS object for a given node. But since the “render” method already has the DOM JSX in a tree fashion, the end result will be a VNode with children and grand children that looks like a tree. 注意“h”函数不会创建完整的树 它仅仅对于给定的node创建了一个JS对象。但是。 最后的结果将会是一个带有子元素和看起来像树的重要子元素的VNode.

参考代码:

“h” :https://github.com/developit/preact/blob/master/src/h.js

VNode: https://github.com/developit/preact/blob/master/src/vnode.js

“render”:https://github.com/developit/preact/blob/master/src/render.js

“buildComponentFromVNode:https://github.com/developit/preact/blob/master/src/vdom/diff.js#L102

好了,让我们看下虚拟DOM如何工作的?

Preact虚拟DOM的算法流程图

下面的流程图展现了组件和子组件如何被Preact创建,更新,删除的。也展现了生命周期的不同阶段,对应的回调函数被调用,像“componentWillMount”。

注意: 我们会一步一步的复习每一部分,如果你会觉复杂,不用担心。

是的,立马理解所有的知识很难。让我们一步一步得通过浏览不同的情景,来复习流程图的不同部分。

注意: 当讨论到关键的生命周期的部分我将会用黄色高亮。

情景 1: APP创建初始化1.1 — 对一个给定的组件创建一个VNode

黄色高亮区域对于一个给定的组件创建虚拟DOM数,初始化处理循环。注意没有为子组件创建虚拟DOM(这是个不同的循环)

黄色区域展示了虚拟DOM的创建

下面这张图片展示了当我们应用第一次加载的时候发生了什么。这个库最终为主要组件“FilteredList”创建了一个带有子元素和属性的VNode。

注意: 它连着调用了生命周期方法“componentWillMount” 和 “render”.(看上面图片绿色的部分)

(click to zoom)

这个时候,我们有了个“div”的父元素,它包含了子节点“input”和“list”。

引用:

大多数的生命周期事件,像componentWillMount,render等等:https://github.com/developit/preact/blob/master/src/vdom/component.js

1.2 — 如果不是一个组件,创建一个真实的DOM

这一步,它仅会对父元素div创建一个真实的DOM,并且对于子节点(“input” 和 “List”)重复这一步骤。

黄色的循环部分展现了子组件的创建。

这一步,下面的图片中仅仅“div”被显示出来了。

引用:

document.createElement: https://github.com/developit/preact/blob/master/src/dom/recycler.js

1.3 — 对所有的子元素重复这一步

这一步,对所有的子元素将会重复这一步。在我们的应用中,会对“input” 和 “List” 重复。

对每一个子元素重复

1.4 — 处理子元素,并且把它加到父元素上.

这一步我们将会处理子树。既然“input”有一个父元素“div”,我们将会把input作为一个子元素加到div中。然后停止,返回创建“List”(第二个div子元素)。

结束处理子树

这一步,我们的应用看起来像下面这样:

注意: “input”被创建后,由于没有任何一个子元素,不会理解循环和创建“List”。它会首先将“input”加入到父元素“div”中,然后返回处理“List”。

引用:

appendChild: https://github.com/developit/preact/blob/master/src/vdom/diff.js

1.5 处理子组件(们)

控制流程回到1.1,对“List”组件开始所有的。但是“List”是一个组件,它调用“List”组件的方法render,得到一组新的虚DOM,像下面这样

对一个子组件重复所有的操作

对List组件重复操作之后,返回VNode像下面这样:

引用:

“buildComponentFromVNode:https://github.com/developit/preact/blob/master/src/vdom/diff.js#L102

1.6 对所有子节点重复1.1到1.4步骤

它会再一次对每一个节点重复上面的步骤。一旦它到达子节点,就会把它加入到节点的父节点,并且重复处理。

重复这一步骤,直到所有的父子节点被创建和添加。

下面的图片展示了每个节点的添加(提示: 深度优先)

真实的DOM树如何被虚拟DOM算法创建的。

1.7 结束处理

这一步,结束处理。它仅对所有的组件调用了“componentDidMount”(从子组件到父组件)并且停止。

重要提示: 一旦所有所有做完之后,一个真实DOM的引用被添加到每个组件的实例上去。这个引用被用来更新(创建,更新,删除)比较,避免重复创建同样的DOM节点。

情景 2: 删除叶子节点

当我们输入“cal” 关键字,确认。将会移除掉第二个list节点,保留所有的父节点。

让我们看下,怎么样看这个情景?

2.1 像之前那样创建VNodes.

当初始化渲染之后,未来的每一个变化都是一个更新。当需要创建VNodes时,更新的周期工作跟创建的周期非常的相似,并且再一次创建所有的VNodes。

既然是一个组件的更新(不是创建),每个组件和子组件都会调用“componentWillReceiveProps”, “shouldComponentUpdate”, 和 “componentWillUpdate”

另外, update cycle, 如果那些元素已经存在不会重复创建真实的DOM。

更新组件的生命周期

引用

removeNode:https://github.com/developit/preact/blob/master/src/dom/index.js#L9

insertBefore:https://github.com/developit/preact/blob/master/src/vdom/diff.js#L253

2.2 用引用的真实DOM,避免创建重复的nodes

像之前提到的,在初始化加载期间,每个组件相对应我们创建的真实DOM树有一个引用。下面这张图片展现了这一刻我们的应用的引用。

显示每一个组件 和 之前的DOM的差异

当虚拟DOM被创建,每个虚拟DOM的属性都会跟真实DOM的属性进行比较如果真实DOM存在,循环处理将会进行下一步

真实DOM已经存在(在更新期间)

引用

innerDiffNode: https://github.com/developit/preact/blob/master/src/vdom/diff.js#L185

2.3 如果他们在真实的DOM中是额外的节点,移除他们

下面的图片展现了真实DOM和虚拟DOM的差异

(click to zoom)

这里有一点儿不同。在真实节点中的“New York”节点被算法移除了像下面流程图那样。当所有工作进行完毕算法也会调用“componentDidUpdate”。

移除DOM节点生命周期

情景 3 — 卸载整个组件

让我们看看在filter组件中输入blabla,既然没有匹配到“California” 和 “New York”, 我们不会渲染子组件“List”,这意味着我们需要卸载整个组件。

如果没有结果的话List组件没有被移除

组件FilteredList的render方法

删除一个组件跟删除一个单一节点差不多,当我们删除一个相对于组件有引用的节点,框架会调用“componentWillUnmount”,然后安全的删除所有的DOM元素。当所有的元素从真实DOM移除,将会调用引用的组件的“componentDidUnmount”方法。

下面的图片显示在真实的DOM“ul”中,“List”组件的引用。

下面流程图的高亮部分展现了移除和卸载组件的过程

移除和卸载组件

posted @ 2018-01-02 17:04  铭绘  阅读(3100)  评论(0编辑  收藏  举报