狀態、屬性、組件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」組件的引用。
下面流程圖的高亮部分展示了移除和卸載組件的過程
移除和卸載組件