React核心内容概括总结 【*】

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

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

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

 

状态css

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.jsxhtml

import React from 'react'; class HelloMessage extends React.Component { render () { return ( <div> Hello, {this.props.name} </div> ) } } export default HelloMessage;

main.jsjava

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控制台会抛出错误。node

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

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节点,查找真实DOMwebpack

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


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

生命周期方法
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”组件的引用。

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

移除和卸载组件

相关文章
相关标签/搜索