利用npx create-react-app my_react
命令建立項目javascript
文章首發於公衆號《前端陽光》,項目已經放到github:github.com/Sunny-lucki… 以爲能夠的話,給個star鼓勵下哈啊哈 有什麼不對的或者建議或者疑惑,歡迎指出啊!立志寫得通俗易懂html
將一些用不到的文件刪除後,目錄變成這樣前端
此時的index.jsjava
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
"sunny",
document.getElementById('root')
);
複製代碼
咱們就能夠把須要引入react和react-dom的改爲本身建立的文件啦react
import React from './react';
import ReactDOM from './react-dom';
ReactDOM.render(
"sunny",
document.getElementById('root')
);
複製代碼
咱們在index.js文件中jquery
ReactDOM.render(
"sunny",
document.getElementById('root')
);
複製代碼
以這樣的方式使用ReactDOM,說明他有render這個方法。git
因此咱們能夠這樣實現react-domgithub
// react-dom.js
let ReactDOM = {
render
}
function render(element,container){
container.innerHTML = `<span>${element}</span>`
}
export default ReactDOM
複製代碼
咱們看下運行結果babel
可喜可賀!萬里長城邁出了第一步markdown
好了,如今咱們給每個 元素打上 一個標記 ,這樣的話 就能夠經過這個標記 辨別出與其餘 元素的關係,也能夠直接經過這標記找到該元素了。
就像下面這張圖同樣,是否是就直接看出0.0和0.1的父節點就是0了呢?
// react-dom.js
let ReactDOM = {
render,
rootIndex:0
}
function render(element,container){
container.innerHTML = `<span data-reactid=${ReactDOM.rootIndex}>${element}</span>`
}
export default ReactDOM
複製代碼
如代碼所示,咱們給每個元素添加了一個標記data-reactid
運行,發現確實標記成功了,哈哈哈
咱們前面的render方法
function render(element,container){
container.innerHTML = `<span data-reactid=${ReactDOM.rootIndex}>${element}</span>`
}
複製代碼
默認傳入的element爲字符串, 可是實際狀況是有多是 文本節點,也有多是DOM節點,也有多是 自定義組件。 因此咱們實現一個createUnit方法,將element傳入,讓它來判斷element是什麼類型的節點,。而後再返回一個被判斷爲某種類型,而且添加了對應的方法和屬性的對象 。例如,咱們的element是字符串類型,那麼就返回一個字符串類型的對象,而這個對象自身有element 屬性和getMarkUp方法,這個getMarkUp方法,將element轉化成真實的dom 其實你也能夠簡單地認爲 createUnit 方法 就是 爲 element 對象添加 一個getMarkUp方法
// react-dom.js
import $ from "jquery"
let ReactDOM = {
render,
rootIndex:0
}
function render(element,container){
let unit = createUnit(element)
let markUp = unit.getMarkUp();// 用來返回HTML標記
$(container).html(markUp)
}
export default ReactDOM
複製代碼
如代碼所示,將element傳入createUnit方法,得到的unit是一個對象
{
_currentElement:element,
getMarkUp(){
...
}
}
複製代碼
再執行 unit的getMarkUp方法,得到到 真實的dom,而後就能夠掛載到container上去啦!
注意,若是傳入render的element是字符串"sunny", 即
import React from './react';
import ReactDOM from './react-dom';
ReactDOM.render(
"sunny",
document.getElementById('root')
);
複製代碼
也就是說傳入createUnit的element是字符串"sunny",那麼返回的unit是
{
_currentElement:"sunny",
getMarkUp(){
}
}
複製代碼
那怎麼寫這個createUnit呢?
咱們建立一個新的文件叫作unit.js
// Unit.js
class Unit{
}
class TextUnit extends Unit{
}
function createUnit(element){
if(typeof element === 'string' || typeof element === "number"){
return new TextUnit(element)
}
}
export {
createUnit
}
複製代碼
如代碼所示,createUnit判斷element是字符串時就 new 一個TextUnit的對象,而後返回出去,這個也就是咱們上面講到的unit對象了。
爲何要 TextUnit 繼承 於 Unit呢?
這是由於 element除了字符串 ,也有多是 原生的標籤,列如div,span等,也有多是咱們自定義的組件,因此咱們先寫 了一個 unit類,這個類實現 這幾種element 所共有的屬性。 而後 具體的 類 ,例如 TextUnit 直接繼承 Unit ,再實現自有的 屬性就行了。
new Unit 獲得的對象應當是這樣的
{
_currentElement:element,
getMarkUp(){
...
}
}
複製代碼
也就是說,這是全部的 種類都有的屬性,因此咱們能夠這樣實現 Unit
class Unit{
constructor(element){
this._currentElement = element
}
getMarkUp(){
throw Error("此方法應該被重寫,不能直接被使用")
}
}
複製代碼
爲何getMarkUp 要 throw Error("此方法應該被重寫,不能直接被使用")
呢?
學過 java或其餘語言的同窗應該秒懂,這是由於getMarkUp但願是被子類重寫的方法,由於每一個子類執行這個方法返回的結果是不同的。
到這一步,咱們只要重寫getMarkUp方法就行了,不過不要忘記,給每個元素添加一個 reactid,至於爲何,已經在上面說過了,也放了一張大圖了哈。
class TextUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
return `<span data-reactid=${reactid}>${this._currentElement}</span>`
}
}
複製代碼
好了,到這裏先看下完整的Unit.js長什麼樣子吧
// Unit.js
class Unit{
constructor(element){
this._currentElement = element
}
getMarkUp(){
throw Error("此方法應該被重寫,不能直接被使用")
}
}
class TextUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
return `<span data-reactid=${reactid}>${this._currentElement}</span>`
}
}
function createUnit(element){
if(typeof element === 'string' || typeof element === "number"){
return new TextUnit(element)
}
}
export {
createUnit
}
複製代碼
咱們在index.js引入 unit測試下
// index.js
import React from './react';
import ReactDOM from './react-dom';
ReactDOM.render(
"sunny",
document.getElementById('root')
);
複製代碼
// react-dom.js
import {createUnit} from './unit'
import $ from "jquery"
let ReactDOM = {
render,
rootIndex:0
}
function render(element,container){
let unit = createUnit(element)
let markUp = unit.getMarkUp(ReactDOM.rootIndex);// 用來返回HTML標記
$(container).html(markUp)
}
export default ReactDOM
複製代碼
意料以內的成功!哈哈哈啊
在第一次學習react的時候,我總會帶着許多疑問。好比看到下面的代碼就會想:爲何咱們只是引入了React,可是並無明顯的看到咱們在其餘地方用,這時我就會想着既然沒有用到,那若是刪除以後會不會受到影響呢?答案固然是不行的。
import React from 'react';
import ReactDOM from 'react-dom';
let element = (
<h1 id="title" className="bg" style={{color: 'red'}}> hello <span>world</span> </h1>
)
console.log({type: element.type, props:element.props})
ReactDOM.render(element,document.getElementById('root'));
複製代碼
當咱們帶着這個問題去研究的時候會發現其實在渲染element的時候調了React.createElement(),因此上面的問題就在這裏找到了答案。
以下面代碼所示,這就是從jsx語法到React.createElement的轉化
<h1 id="title" className="bg" style={{color: 'red'}}>
hello
<span>world</span>
</h1>
//上面的這段代碼很簡單,可是咱們都知道react是所謂的虛擬dom,固然不可能就是咱們看到的這樣。當咱們將上面的代碼通過babel轉譯後,咱們再看看
React.createElement("h1", {
id: "title",
className: "bg",
style: {
color: 'red'
}
}, "hello", React.createElement("span", null, "world"));
複製代碼
document有createElement()方法,React也有createElement()方法,下面就來介紹React的createElement()方法。
var reactElement = ReactElement.createElement(
... // 標籤名稱字符串/ReactClass,
... // [元素的屬性值對對象],
... // [元素的子節點]
)
複製代碼
一、參數:
1)第一個參數:能夠是一個html標籤名稱字符串,也能夠是一個ReactClass(必須);
2)第二個參數:元素的屬性值對對象(可選),這些屬性能夠經過this.props.*來調用;
3)第三個參數開始:元素的子節點(可選)。
二、返回值:
一個給定類型的ReactElement元素
咱們能夠改下咱們的index.js
// index.js
import React from './react';
import ReactDOM from './react-dom';
var li1 = React.createElement('li', {onClick:()=>{alert("click")}}, 'First');
var li2 = React.createElement('li', {}, 'Second');
var li3 = React.createElement('li', {}, 'Third');
var ul = React.createElement('ul', {className: 'list'}, li1, li2, li3);
console.log(ul);
ReactDOM.render(ul,document.getElementById('root'))
複製代碼
能夠就看下 ul 最終的打印 期待結果
由此 ,咱們只知道了,ReactElement.createElement方法將生產一個給定類型的ReactElement元素,而後這個對象被傳入 render方法,而後進行了上面講到的 createUnit和getMarkUp操做。
通過上面的講解,咱們大概已經知道React.createElement方法的做用了,如今就來看看是怎麼實現的
咱們建立了一個新的文件element.js
// element.js
class Element {
constructor(type,props){
this.type = type
this.props = props
}
}
function createElement(type,props={},...children){
props.children = children || [];
return new Element(type,props)
}
export {
Element,
createElement
}
複製代碼
咱們 定義了一個 Element 類 ,而後在createElement方法裏建立了這個類的對象, 而且return出去了
沒錯,這個對象就是上面所說的給定類型的ReactElement元素,也就是下面這張圖所顯示的
咱們應當是這樣React.createElement()調用這個方法的,因此咱們要把這個方法掛載到react身上。
咱們前面尚未實現react.js
其實,很簡單,就是返回一個React對象,這個對象有createElement方法
// react.js
import {createElement} from "./element"
const React = {
createElement
}
export default React
複製代碼
上面實現了 createElement返回 給定類型的ReactElement元素 後,就將改元素傳入,render方法,所以 就會通過 createUnit方法, createUnit方法判斷是屬於什麼類型的 元素,以下面代碼
// Unit.js
import {Element} from "./element" // 新增代碼
class Unit{
constructor(element){
this._currentElement = element
}
getMarkUp(){
throw Error("此方法應該被重寫,不能直接被使用")
}
}
class TextUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
return `<span data-reactid=${reactid}>${this._currentElement}</span>`
}
}
function createUnit(element){
if(typeof element === 'string' || typeof element === "number"){
return new TextUnit(element)
}
// 新增代碼
if(element instanceof Element && typeof element.type === "string"){
return new NativeUnit(element)
}
}
export {
createUnit
}
複製代碼
好了,如今咱們來實現NativeUnit類,其實主要就是實現NativeUnit的getMarkUp方法
class NativeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type,props} = this._currentElement;
}
}
複製代碼
要明確的一點是,NativeUnit 的getMarkUp方法,是要把 這樣一個element 對象轉化爲 真實的dom的
所以,咱們能夠這樣完善getMarkUp方法
class NativeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type,props} = this._currentElement;
let tagStart = `<${type} `
let childString = ''
let tagEnd = `</${type}>`
for(let propName in props){
if(/^on[A-Z]/.test(propName)){ // 添加綁定事件
}else if(propName === 'style'){ // 若是是一個樣式對象
}else if(propName === 'className'){ // 若是是一個類名
}else if(propName === 'children'){ // 若是是子元素
}else { // 其餘 自定義的屬性 例如 reactid
tagStart += (` ${propName}=${props[propName]} `)
}
}
return tagStart+'>' + childString +tagEnd
}
}
複製代碼
這只是 大致上的 一個實現 ,其實就是 把標籤 和屬性 以及 子元素 拼接成 字符串,而後返回出去。
咱們測試下,如今有沒有 把ul 渲染出來
// index.js
import React from './react';
import ReactDOM from './react-dom';
var li1 = React.createElement('li', {}, 'First');
var li2 = React.createElement('li', {}, 'Second');
var li3 = React.createElement('li', {}, 'Third');
var ul = React.createElement('ul', {className: 'list'}, li1, li2, li3);
console.log(ul);
ReactDOM.render(ul,document.getElementById('root'))
複製代碼
發現確實成功渲染出來了,可是 屬性和 子元素尚未,這是由於咱們 還沒實現 具體 的功能。
如今咱們來實現事件綁定 功能
class NativeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type,props} = this._currentElement;
let tagStart = `<${type} data-reactid="${this._reactid}"`
let childString = ''
let tagEnd = `</${type}>`
for(let propName in props){
// 新增代碼
if(/^on[A-Z]/.test(propName)){ // 添加綁定事件
let eventName = propName.slice(2).toLowerCase(); // 獲取click
$(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,props[propName])
}else if(propName === 'style'){ // 若是是一個樣式對象
}else if(propName === 'className'){ // 若是是一個類名
}else if(propName === 'children'){ // 若是是子元素
}else { // 其餘 自定義的屬性 例如 reactid
}
}
return tagStart+'>' + childString +tagEnd
}
}
複製代碼
在這裏,咱們是用了事件代理的模式,之因此用事件代理,是由於這些標籤元素還沒被渲染到頁面上,但咱們又必須提早綁定事件,因此須要用到事件代理
接下來,實現 樣式對象的綁定
class NativeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type,props} = this._currentElement;
let tagStart = `<${type} data-reactid="${this._reactid}"`
let childString = ''
let tagEnd = `</${type}>`
for(let propName in props){
if(/^on[A-Z]/.test(propName)){ // 添加綁定事件
...
}else if(propName === 'style'){ // 若是是一個樣式對象
let styleObj = props[propName]
let styles = Object.entries(styleObj).map(([attr, value]) => {
return `${attr.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`)}:${value}`;
}).join(';')
tagStart += (` style="${styles}" `)
}else if(propName === 'className'){ // 若是是一個類名
}else if(propName === 'children'){ // 若是是子元素
}else { // 其餘 自定義的屬性 例如 reactid
}
}
return tagStart+'>' + childString +tagEnd
}
}
複製代碼
這裏 其實就是把
{style:{backgroundColor:"red"}}
複製代碼
對象中的 style這個對象 屬性拿出來,
而後把backgroundColor 經過正則 變化成background-color,
而後再拼接到tagStart中。
接下來再實現className,發現這個也太簡單了吧
class NativeUnit extends Unit {
getMarkUp(reactid) {
this._reactid = reactid
let { type, props } = this._currentElement;
let tagStart = `<${type} data-reactid="${this._reactid}"`
let childString = ''
let tagEnd = `</${type}>`
for (let propName in props) {
if (/^on[A-Z]/.test(propName)) { // 添加綁定事件
...
} else if (propName === 'style') { // 若是是一個樣式對象
...
} else if (propName === 'className') { // 若是是一個類名
tagStart += (` class="${props[propName]}"`)
} else if (propName === 'children') { // 若是是子元素
...
} else { // 其餘 自定義的屬性 例如 reactid
...
}
}
return tagStart + '>' + childString + tagEnd
}
}
複製代碼
爲何這麼簡單呢? 由於只須要把
className: 'list'
複製代碼
中的className變化成 class就能夠了。OMG!!
接下來,是時候實現子元素的拼接了哈
class NativeUnit extends Unit {
getMarkUp(reactid) {
this._reactid = reactid
let { type, props } = this._currentElement;
let tagStart = `<${type} data-reactid="${this._reactid}"`
let childString = ''
let tagEnd = `</${type}>`
for (let propName in props) {
if (/^on[A-Z]/.test(propName)) { // 添加綁定事件
...
} else if (propName === 'style') { // 若是是一個樣式對象
...
} else if (propName === 'className') { // 若是是一個類名
...
} else if (propName === 'children') { // 若是是子元素
let children = props[propName];
children.forEach((child, index) => {
let childUnit = createUnit(child); // 多是字符串 ,也多是原生標籤,也多是自定義屬性
let childMarkUp = childUnit.getMarkUp(`${this._reactid}.${index}`)
childString += childMarkUp;
})
} else { // 其餘 自定義的屬性 例如 reactid
}
}
return tagStart + '>' + childString + tagEnd
}
}
複製代碼
發現子元素 ,其實只要進行遞歸操做,也就是將子元素傳進createUnit,把返回的childUnit 經過childMarkUp 方法變成 真實動,再拼接到childString 就行了。 其實想一想也挺簡單,就相似深拷貝的操做。
好了,接下來就是 其餘屬性了
class NativeUnit extends Unit {
getMarkUp(reactid) {
this._reactid = reactid
let { type, props } = this._currentElement;
let tagStart = `<${type} data-reactid="${this._reactid}"`
let childString = ''
let tagEnd = `</${type}>`
for (let propName in props) {
if (/^on[A-Z]/.test(propName)) { // 添加綁定事件
...
} else if (propName === 'style') { // 若是是一個樣式對象
...
} else if (propName === 'className') { // 若是是一個類名
...
} else if (propName === 'children') { // 若是是子元素
...
} else { // 其餘 自定義的屬性 例如 reactid
tagStart += (` ${propName}=${props[propName]} `)
}
}
return tagStart + '>' + childString + tagEnd
}
}
複製代碼
其餘屬性直接就拼上去就行了哈哈哈
好了。如今咱們已經完成了NativeUini的getMarkUp方法。咱們來測試一下是否成功了沒有吧! 害,不出所料地成功了。
接下來咱們看看自定義組件是怎麼被渲染的,例以下面的Counter組件
// index.js
class Counter extends React.Component{
constructor(props){
super(props)
this.state = {number:0};
}
render(){
let p = React.createElement('p',{style:{color:'red'}},this.state.number);
let button = React.createElement('button',{},"+")
return React.createElement('div',{id:'counter'},p,button)
}
}
let element = React.createElement(Counter,{name:"計時器"})
ReactDOM.render(element,document.getElementById('root'))
複製代碼
咱們發現自定義組件好像須要繼承React.Component。這是爲何呢?
我以前一直誤認爲全部的生命週期都是從Component繼承過來的,也許有不少小夥伴都和我同樣有這樣的誤解,直到我看了Component源碼才恍然大悟,原來咱們用的setState和forceUpdate方法是來源於這裏
知道這個緣由後,咱們就能夠先簡單地實現React.Component了
// component.js
class Component{
constructor(props){
this.props = props
}
}
export {
Component
}
複製代碼
而後再引入react中便可
// react.js
import {createElement} from "./element"
import {Component} from "./component"
const React = {
createElement,
Component
}
export default React
複製代碼
跟 處理NativeUnit同樣,先經過createUnit判斷element是屬於什麼類型,若是是自定義組件就 return CompositeUnit
// Unit.js
import { Element } from "./element" // 新增代碼
import $ from "jquery"
class Unit {
constructor(element) {
this._currentElement = element
}
getMarkUp() {
throw Error("此方法應該被重寫,不能直接被使用")
}
}
class TextUnit extends Unit {
}
class NativeUnit extends Unit {
}
function createUnit(element) {
if (typeof element === 'string' || typeof element === "number") {
return new TextUnit(element)
}
if (element instanceof Element && typeof element.type === "string") {
return new NativeUnit(element)
}
// 新增代碼
if(element instanceof Element && typeof element.type === 'function'){
return new CompositeUnit(element)
}
}
export {
createUnit
}
複製代碼
爲何是用 typeof element.type === 'function'
來判斷 呢? 由於Counter是 一個類,而類在js中的本質就是function
好了,接下來實現一下CompositeUnit類
class CompositeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type:Component,props} = this._currentElement // 實際上,在例子中type === Counter
let componentInstance = new Component(props);
let renderElement = componentInstance.render();
let renderUnit = createUnit(renderElement);
return renderUnit.getMarkUp(this._reactid)
}
}
複製代碼
咦,好簡短 啊,不過 沒那麼 簡單,可是讓 個人三寸不爛之舌來說解一下,包懂
此時的_currentElement 是:
{
type:Counter,
props:{}
}
複製代碼
let {type:Component,props} = this._currentElement
// 實際上,在例子中type就是Counter new Component(props);
其實就是new Counter
。
也就是咱們上面例子中寫的
class Counter extends React.Component{
constructor(props){
super(props)
this.state = {number:0};
}
render(){
let p = React.createElement('p',{style:{color:'red'}},this.state.number);
let button = React.createElement('button',{},"+")
return React.createElement('div',{id:'counter'},p,button)
}
}
let element = React.createElement(Counter,{name:"計時器"})
ReactDOM.render(element,document.getElementById('root'))
複製代碼
可想而知 ,經過new Counter就得到了Counter的實例
也就是componentInstance ,而每個Counter的實例都會有render方法,因此執行componentInstance.render()
就得到一個給定類型的ReactElement元素(好熟悉的一句話,對,咱們在上面講到過)。
而後就把這個ReactElement元素對象傳給createUnit,得到一個具備getMarkUp的renderUnit 對象, 而後就能夠執行renderUnit.getMarkUp(this._reactid)
得到真實dom,就能夠返回了。
其實,仔細想一想,就會發現,在
let renderUnit = createUnit(renderElement);
複製代碼
以前,咱們是在處理自定義組件Counter。
而到了
let renderUnit = createUnit(renderElement);
複製代碼
這一步,其實就是在處理NativeUnit。(細思極恐。。)
好了,測試一下 發現確實成功了。
咱們在以前的例子上添加個componentWillMount 生命週期函數吧
// index.js
import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component{
constructor(props){
super(props)
this.state = {number:0};
}
componentWillMount(){
console.log("陽光你好,我是componentWillMount");
}
render(){
let p = React.createElement('p',{style:{color:'red'}},this.state.number);
let button = React.createElement('button',{},"+")
return React.createElement('div',{id:'counter'},p,button)
}
}
let element = React.createElement(Counter,{name:"計時器"})
ReactDOM.render(element,document.getElementById('root'))
複製代碼
咱們知道componentWillMount 實在組件渲染前執行的,因此咱們能夠在render以前執行這個生命週期函數
class CompositeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type:Component,props} = this._currentElement // 實際上,在例子中type === Counter
let componentInstance = new Component(props);
componentInstance.componentWillMount && componentInstance.componentWillMount() // 添加生命週期函數
let renderElement = componentInstance.render();
let renderUnit = createUnit(renderElement);
return renderUnit.getMarkUp(this._reactid)
}
}
複製代碼
可能聰明的小夥伴會問,不是說componentWillMount是在組件從新渲染前執行的嗎?那組件沒掛到頁面上應該都是渲染前,因此componentWillMount也能夠在return renderUnit.getMarkUp(this._reactid)
前執行啊。
其實要回答這個問題,倒不如回答另外一個問題:
父組件的componentWillMount和子組件的componentWillMount哪一個先執行。
答案是父組件先執行。
這是由於在父組件中會先執行 父組件的componentWillMount ,而後執行componentInstance.render();
的時候,會解析子組件,而後又進入子組件的getMarkUp。又執行子組件的componentWillMount 。
若要回答 爲何componentWillMount 要在 render函數執行前執行,只能說,react就是這麼設計的哈哈哈
衆所周知,componentDidMount是在組件渲染,也就是掛載到頁面後才執行的。
因此,咱們能夠在返回組件的真實dom以前 就監聽 一個mounted事件,這個事件執行componentDidMount方法。
class CompositeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type:Component,props} = this._currentElement // 實際上,在例子中type === Counter
let componentInstance = new Component(props);
componentInstance.componentWillMount && componentInstance.componentWillMount()
let renderElement = componentInstance.render();
let renderUnit = createUnit(renderElement);
$(document).on("mounted",()=>{
componentInstance.componentDidMount && componentInstance.componentDidMount()
})
return renderUnit.getMarkUp(this._reactid)
}
}
複製代碼
而後 再在 把組件的dom掛載到 頁面上後再觸發這個 mounted事件
// react-dom.js
import {createUnit} from './unit'
import $ from "jquery"
let ReactDOM = {
render,
rootIndex:0
}
function render(element,container){
let unit = createUnit(element)
let markUp = unit.getMarkUp(ReactDOM.rootIndex);// 用來返回HTML標記
$(container).html(markUp)
$(document).trigger("mounted")
}
export default ReactDOM
複製代碼
由此依賴,就實現了,componentDidMount 生命週期函數,哈哈哈。
測試一下,成功了沒有哈 啊,一如既往的成功,可能好奇的你問我爲何每次測試都成功,那是由於,不成功也被我調試到成功了。
爲了下面 實現 setState 功能,咱們 修改一下 CompositeUnit 的getMarkUp方法。
class CompositeUnit extends Unit{
getMarkUp(reactid){
this._reactid = reactid
let {type:Component,props} = this._currentElement // 實際上,在例子中type === Counter
let componentInstance = this._componentInstance = new Component(props); // 把 實例對象 保存到這個 當前的 unit
componentInstance._currentUnit = this // 把 unit 掛到 實例componentInstance
componentInstance.componentWillMount && componentInstance.componentWillMount()
let renderElement = componentInstance.render();
let renderUnit = this._renderUnit = createUnit(renderElement); // 把渲染內容對象也掛載到當前 unit
$(document).on("mounted",()=>{
componentInstance.componentDidMount && componentInstance.componentDidMount()
})
return renderUnit.getMarkUp(this._reactid)
}
}
複製代碼
咱們爲這個 CompositeUnit 的實例添加了
另外,咱們也經過
componentInstance._currentUnit = this // 把 unit 掛到 實例componentInstance
複製代碼
把當前 的unit 掛載到了 組件實例componentInstance身上。
可見 組件的實例保存了 當前 unit,當前的unit也保存了組件實例
咱們看下面的例子,每隔一秒鐘就number+1
// index.js
import React from './react';
import ReactDOM from './react-dom';
import $ from 'jquery'
class Counter extends React.Component{
constructor(props){
super(props)
this.state = {number:0};
}
componentWillMount(){
console.log("陽光你好,我是componentWillMount");
$(document).on("mounted",()=>{
console.log(456);
})
}
componentDidMount(){
setInterval(()=>{
this.setState({number:this.state.number+1})
},1000)
}
render(){
return this.state.number
}
}
let element = React.createElement(Counter,{name:"計時器"})
ReactDOM.render(element,document.getElementById('root'))
複製代碼
前面說到,setState方法是從Component組件繼承過來的。因此咱們給Component組件添加setState方法
// component.js
class Component{
constructor(props){
this.props = props
}
setState(partialState){
// 第一個參數是新的元素,第二個參數是新的狀態
this._currentUnit.update(null,partialState)
}
}
export {
Component
}
複製代碼
咱們發現原來是在setState方法裏調用了當前實例的對應的unit的update方法,它傳進去了 部分state的值。
看到這裏,咱們就知道了,咱們須要回到 CompositeUnit類添加一個update方法。
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
}
getMarkUp(reactid){
...
}
}
複製代碼
咱們首先 更換了_currentElement的值,這裏爲何會有 有或者沒有nextElement的狀況呢?
(主要就是由於,若是 _currentElement 是 字符串或者數字的話,那麼它就須要 傳nextElement 來替換掉舊的 _currentElement 。而若是不是字符串或者數字的話,是不須要傳的。而CompositeUnit 一定是組件的,因此不用傳nextElement )。
接着,咱們 經過下面這句代碼獲取了最新的state,而且更新了組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
複製代碼
獲取 最新的 props跟獲取state的方式不同,props是跟_currentElement 綁定在一塊兒的,因此獲取最新的props是經過
let nextProps = this._currentElement.props
複製代碼
接下來,咱們要先獲取新舊的渲染元素,而後拿來比較,怎麼獲取呢?
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
// 下面要進行比較更新
// 先獲得上次渲染的unit
let preRenderedUnitInstance = this._renderUnit;
// 經過上次渲染的unit獲得上次渲染的元素
let preRenderElement = preRenderedUnitInstance._currentElement
// 獲得最新的渲染元素
let nextRenderElement = this._componentInstance.render()
}
getMarkUp(reactid){
}
}
複製代碼
咱們先獲得上次渲染的unit,再經過上次渲染的unit獲得上次渲染的元素preRenderElement ,
再經過this._componentInstance.render()
獲得下次渲染的元素nextRenderElement 。
接下來就能夠進行比較這兩個元素了
咱們首先會判斷要不要進行深度比較。
若是不是進行深度比較就很是簡單
直接獲取新的渲染unit,而後經過getMarkUp得到要渲染的dom,接着就把當前的組件裏的dom元素替換掉
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
// 下面要進行比較更新
// 先獲得上次渲染的unit
let preRenderedUnitInstance = this._renderUnit;
// 經過上次渲染的unit獲得上次渲染的元素
let preRenderElement = preRenderedUnitInstance._currentElement
// 獲得最新的渲染元素
let nextRenderElement = this._componentInstance.render()
// 若是新舊兩個元素類型同樣,則能夠進行深度比較,若是不同,直接幹掉老的元素,新建新的
if(shouldDeepCompare(preRenderElement,nextRenderElement)){
}else{
this._renderUnit = createUnit(nextRenderElement)
let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)
$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)
}
}
getMarkUp(reactid){
}
}
複製代碼
咱們先簡單地寫一下shouldDeepCompare方法,直接return false,來測試一下 非深度比較,是否可以正確執行
function shouldDeepCompare(){
return false
}
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
// 下面要進行比較更新
// 先獲得上次渲染的unit
let preRenderedUnitInstance = this._renderUnit;
// 經過上次渲染的unit獲得上次渲染的元素
let preRenderElement = preRenderedUnitInstance._currentElement
// 獲得最新的渲染元素
let nextRenderElement = this._componentInstance.render()
// 若是新舊兩個元素類型同樣,則能夠進行深度比較,若是不同,直接幹掉老的元素,新建新的
if(shouldDeepCompare(preRenderElement,nextRenderElement)){
}else{
this._renderUnit = createUnit(nextRenderElement)
let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)
$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)
}
}
getMarkUp(reactid){
}
}
複製代碼
發現確實成功了。
若是能夠進行深度比較呢?
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
// 下面要進行比較更新
// 先獲得上次渲染的unit
let preRenderedUnitInstance = this._renderUnit;
// 經過上次渲染的unit獲得上次渲染的元素
let preRenderElement = preRenderedUnitInstance._currentElement
// 獲得最新的渲染元素
let nextRenderElement = this._componentInstance.render()
// 若是新舊兩個元素類型同樣,則能夠進行深度比較,若是不同,直接幹掉老的元素,新建新的
if(shouldDeepCompare(preRenderElement,nextRenderElement)){
// 若是能夠進行深度比較,則把更新的nextRenderElement傳進去
preRenderedUnitInstance.update(nextRenderElement)
}else{
this._renderUnit = createUnit(nextRenderElement)
let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)
$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)
}
}
getMarkUp(reactid){
}
}
複製代碼
若是能夠深度,就執行
preRenderedUnitInstance.update(nextRenderElement)
複製代碼
這是什麼意思?
咱們當前是在執行渲染Counter的話,那preRenderedUnitInstance 是什麼呢?
沒錯!它是Counter組件 執行render方法 ,再執行createUnit得到的
這個字符串的 unit
而後調用了這個 unit的 update方法
注意,這裏 的unit是字符串的 unit,也就是說是 TextUnit
因此咱們須要實現 TextUnit 的update 方法
class TextUnit extends Unit {
getMarkUp(reactid) {
this._reactid = reactid
return `<span data-reactid=${reactid}>${this._currentElement}</span>`
}
update(nextElement){
debugger
if(this._currentElement !== nextElement){
this._currentElement = nextElement
$(`[data-reactid="${this._reactid}"]`).html(nextElement)
}
}
}
複製代碼
TextUnit 的update方法很是簡單,先判斷 渲染內容有沒有變化,有的話就 替換點字符串的內容
並把當前unit 的_currentElement 替換成最新的nextElement
咱們簡單的把shouldDeepCompare 改爲 return true,測試一下深度比較
function shouldDeepCompare(){
return true
}
複製代碼
一如既往成功
咱們知道有個shouldComponentUpdate,用來決定要不要 重渲染 該組件的
shouldComponentUpdate(nextProps, nextState) {
return nextState.someData !== this.state.someData
}
複製代碼
顯然,它要咱們傳入 兩個參數,分別是 組件更新後的nextProps和nextState
而在 仍是上面,實現 update的過程當中,咱們已經獲得了nextState 和nextProps
class CompositeUnit extends Unit{
update(nextElement,partialState){
。。。
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
// 下面要進行比較更新
。。。
}
getMarkUp(reactid){
}
}
複製代碼
因此,咱們能夠在update裏執行shouldComponentUpdate方法,來肯定要不要從新渲染組件
class CompositeUnit extends Unit{
update(nextElement,partialState){
// 有傳新元素的話就更新currentElement爲新的元素
this._currentElement = nextElement || this._currentElement;
// 獲取新的狀態,而且更新組件的state
let nextState = this._componentInstance.state = Object.assign(this._componentInstance.state,partialState);
// 新的屬性對象
let nextProps = this._currentElement.props
if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){
return;
}
// 下面要進行比較更新
// 先獲得上次渲染的unit
let preRenderedUnitInstance = this._renderUnit;
// 經過上次渲染的unit獲得上次渲染的元素
let preRenderElement = preRenderedUnitInstance._currentElement
// 獲得最新的渲染元素
let nextRenderElement = this._componentInstance.render()
// 若是新舊兩個元素類型同樣,則能夠進行深度比較,若是不同,直接幹掉老的元素,新建新的
if(shouldDeepCompare(preRenderElement,nextRenderElement)){
// 若是能夠進行深度比較,則把更新的工做交給上次渲染出來的那個Element元素對應的unit來處理
preRenderedUnitInstance.update(nextRenderElement)
}else{
this._renderUnit = createUnit(nextRenderElement)
let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)
$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)
}
}
getMarkUp(reactid){
}
}
複製代碼
so Easy。
只要在更新後觸發這個事件就行了
class CompositeUnit extends Unit{
update(nextElement,partialState){
if(this._componentInstance.shouldComponentUpdate && !this._componentInstance.shouldComponentUpdate(nextProps,nextState)){
return;
}
if(shouldDeepCompare(preRenderElement,nextRenderElement)){
// 若是能夠進行深度比較,則把更新的工做交給上次渲染出來的那個Element元素對應的unit來處理
preRenderedUnitInstance.update(nextRenderElement)
this._componentInstance.componentDidUpdate && this._componentInstance.componentDidUpdate()
}else{
this._renderUnit = createUnit(nextRenderElement)
let nextMarkUp = this._renderUnit.getMarkUp(this._reactid)
$(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp)
}
}
getMarkUp(reactid){
}
}
複製代碼
判斷是否須要深比較極其簡單,只須要判斷 oldElement 和newElement 是否 都是字符串或者數字,這種類型的就走深比較
接着判斷 oldElement 和newElement 是否 都是 Element類型,不是的話就return false,是的 再判斷 type是否相同(即判斷是不是同個組件,是的話 return true)
其餘狀況都return false
function shouldDeepCompare(oldElement,newElement){
if(oldElement != null && newElement != null){
let oldType = typeof oldElement
let newType = typeof newElement
if((oldType === 'string' || oldType === "number")&&(newType === "string" || newType === "number")){
return true
}
if(oldElement instanceof Element && newElement instanceof Element){
return oldElement.type === newElement.type
}
}
return false
}
複製代碼
文章首發於公衆號《前端陽光》,項目已經放到github:github.com/Sunny-lucki… 以爲能夠的話,給個star鼓勵下哈啊哈