状态、属性、组件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;
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-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-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-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
做用是在单页面中进行组件间的页面跳转。
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}
流程图展示VDOM在Preact中如何工做
虚拟DOM (VDOM 也叫 VNode)很是有魔力 ✨ 可是也很是复杂和难以理解😱. React, 在Preact和一些相似的JS库的核心代码中使用. 不幸的是我发现没有一篇好的文章或者文档简洁明了的来介绍它。 所以我决定本身写一篇.
注意: 这篇文章很长. 我已经添加尽量多的图片来使其理解更简单一些,可是我发现这样的话,文章更长了.
我用的是 Preact’s 代码 和 VDOM,由于它很小,你能够在未来很温馨的阅读它。 可是我相信几乎全部的概念一样适用于React.
我但愿你读完这篇文章后,可以很容易的理解像React或者Preact的库,甚至对你写出相似的库也是有帮助的
在这篇博客中,我将会举一些简单示例,而且复习一下不一样的小知识,给你一个关于它们到底如何工做的概念。特别地,我会复习:
Babel 和 JSX
建立一个VNode - 一个简单的虚拟DOM元素
处理组件及子组件
初始化渲染而且建立一个DOM元素
从新渲染
移除DOM元素
替换DOM元素
关于这个demo:
这是一个简单过滤搜索应用, 仅包含有两个组件“FilteredList” 和 “List”。这个List组件渲染列表项(默认是“California” 和 “New York”)。这个应用有一个搜索的区域,能够根据字母来过滤列表项。很是的直观。
相关图片(点击放大,查看更多细节,原文在medium里是能够放大的,这里貌似不行)
大图
高级一点儿,咱们用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”组件的引用。
下面流程图的高亮部分展示了移除和卸载组件的过程
移除和卸载组件