React
是一款用於構建使用者介面的 JavaScript
庫。它以宣告式編寫 UI
,建立擁有各自狀態的元件,再由元件構成更加複雜的介面。
一、前言
在 React
中我們使用 JSX
語法來替代常規的 JavaScript
,並通過 Babel
編譯,比如例子1:
// JSX語法
let str = <h1 className="title">hello</h1>
// Babel編譯後
var str = React.createElement(
"h1",
{ className: "title" },
"hello"
)
複製程式碼
然後我們控制檯輸出 str
,得到結果如下:
// JSX語法
let App = function MyComponent (props) {
return <h1>hello{props.value}</h1>
}
// Babel編譯後
var App = function MyComponent(props) {
return React.createElement(
"h1",
null,
"hello",
props.value
)
}
複製程式碼
控制檯輸出 App
,得到結果如下:
這個物件就是虛擬 DOM
物件,最後通過 ReactDOM.render
方法將虛擬 DOM
解析渲染到頁面上。下面我們就分別來實現 createElement
和 render
方法。
二、createElement 實現
此方法就是實現一個資料結構,把 JSX
編譯後的結構以巢狀形式儲存在資料結構物件中,實現程式碼如下:
/**
* Create and return a new ReactElement of the given type.
*/
function createElement (type, props, ...children) {
return {
type,
props: {
...props,
children: children.length <= 1 ? children[0] : children
}
}
}
複製程式碼
三、render 實現
render
函式就是解析虛擬 DOM
,然後通過 appendChild
渲染到頁面上,實現程式碼如下:
/**
* 將真實dom渲染到頁面
* @param {*虛擬dom} vnode
* @param {*頁面容器} container
* @param {*渲染後的回撥} callback
*/
function render (vnode, container, callback) {
container.appendChild(_render(vnode))
callback && callback()
}
/**
* 將虛擬dom轉成真實節點
* @param {*虛擬dom} vnode
*/
function _render (vnode) {}
複製程式碼
但是虛擬 DOM
的有三種型別,不同型別有不同的渲染方式,下面我們分別來分析一下。
1、字串型別
字串型別即是文字,比如上面的 hello
,直接建立文字節點返回即可,實現程式碼如下:
function _render (vnode) {
// 如果是普通字元
if (util.isString(vnode) || util.isNumber(vnode)) {
return document.createTextNode(vnode)
}
}
複製程式碼
2、標籤型別
常見標籤如 div/h1/span
等,建立元素標籤後需要遍歷設定屬性,實現程式碼如下:
function _render (vnode) {
let { type, props } = vnode
let { children, ...attrs } = props
// 如果是標籤元素,如 div span
let dom = document.createElement(type)
if (children) {
if (util.isArray(children)) {
children.forEach(child => {
render(child, dom)
})
} else {
dom.appendChild(document.createTextNode(children))
}
}
// 設定屬性
setAttributes(attrs, dom)
return dom
}
複製程式碼
3、函式型別
函式型別也分二種情況,一種是無狀態普通函式即函式元件,另一種則是繼承了 React.Component
類的有狀態函式,即類元件。開始之前需要定義一個 Component
父類以便繼承,實現程式碼如下:
/**
* Create React.Component class
*/
class Component {
constructor (props) {
this.props = props
this.state = {}
}
componentWillMount () {
console.log('Component: componentWillMount')
}
componentDidMount () {
console.log('Component: componentDidMount')
}
setState () {
console.log('Component: setState')
}
}
複製程式碼
不管哪種元件,為了保證行為統一性,我們可以分別經過元件建立,元件渲染,然後返回真實 DOM
節點,最後渲染到頁面上的步驟。實現程式碼如下:
function _render (vnode) {
let { type, props } = vnode
let { children, ...attrs } = props
// 如果是函式,說明是元件(包括函式元件和類元件)
if (util.isFunction(type)) {
// 建立元件
let comp = createComponent(type, props)
comp.props = props
// 渲染元件
let dom = renderComponent(comp)
comp.dom = dom
// 設定屬性
setAttributes(attrs, dom)
return dom
}
}
// 建立元件
function createComponent (comp, props) {
// 如果是類元件,需要new一個例項,可以呼叫render方法
if (comp.prototype.render) {
comp = new comp(props)
} else {
comp.render = function () {
return comp(props)
}
}
return comp
}
// 渲染元件:執行元件的render方法,然後通過_render將虛擬dom轉成真實dom,最後返回真實節點
function renderComponent (comp) {
let dom
// 如果未渲染過元件
if (!comp.dom) {
if (comp.componentWillMount) {
comp.componentWillMount()
}
}
dom = _render(comp.render())
if (comp.componentDidMount) {
comp.componentDidMount()
}
return dom
}
複製程式碼
到這裡解析和渲染功能大體完成,下面介紹一下如何修改元件狀態。
四、setState 實現
在上面我們通過 setAttribute
已經將事件繫結到元素上,事件中很有可能會修改元件狀態,比如this.setState({a: this.state.a + 1})
語句,以此更新檢視。不過通過上面虛擬 DOM
解析成真實 DOM
的學習,在這裡先忽略 DOM Diff
功能,重新渲染還是很好實現的,程式碼如下:
class Component {
setState (newState) {
console.log('Component: setState')
Object.assign(this.state, newState)
let old = this.dom
// renderComponent:整體重新渲染,然後返回真實節點
let newDom = renderComponent(this)
old.parentNode.replaceChild(newDom, old)
this.dom = newDom
}
}
複製程式碼
好了,今天的學習就先到這裡吧???~