上一篇文章咱們主要實現了 JSX 在 WebGL
上的渲染與更新,對 虛擬DOM 和 Diff 有了更深的瞭解,但相比於咱們使用的 React
,還缺少了之中很重要的一環 --- 組件模式。前端
想必你們能認同,React組件(Component
)具備 強大的功能,高拓展性和高解耦性,在其基礎上構建的各類 UI
組件框架徹底改變了傳統的 Web 開發模式,成爲了Web 中的大型複雜應用提供了一種很好的構建模式和保障,也讓咱們的開發效率也有了質的變化。node
在這篇文章中,咱們會在上一篇的實現基礎上,加入 React
組件模式,並在實現的過程當中適時的去講解一些原理和思惟,也有利於你們 由淺入深的理解 和 編程思惟上的提高。react
因爲下篇是徹底以上篇做爲基礎的。因此若是你還沒看過上篇,請優先猛戳:git
React 實踐揭祕之旅,中高級前端必備(上) ->github
代碼複用性,向來是編程領域一個核心的理念。咱們最常使用 函數、類 方式進行代碼的封裝,但這裏有個痛點:web
Web 中 UI 與 邏輯 的分離的特性,致使較難優雅地整合封裝。編程
一般咱們須要引 JS
、CSS
,再在 HTML
中按規定書寫結構,最後再初始化庫。整個過程十分割裂,且不優雅。數組
而 React Component
幫咱們解決了這個痛點,即保持了 動態UI 與 邏輯 的分離,又有了全新的整合方式。從而讓 UI 組件變得很是的 高效易用,只須要在結構中以標籤的形式引入便可。瀏覽器
咱們就繼續在上篇文章的基礎上,加入 Component
的特性。基於 JSX
的變量傳遞,咱們只須要實現一個 Component
類,並針對性地去完成組件的 渲染和更新 便可。緩存
TIPs:
因爲組件的加入,這時咱們的 虛擬DOM(VNode) 就包含了兩種類型: 組件節點(compVNode) 和 元素節點(elVNode)。後續都會以此區分,便於講解。
// 組件基類
class Component {
// 經過保持三份不一樣的時期的快照
// 有利於對組件的狀態及屬性的管理和追蹤
// 屬性
public __prevProps
public props = {}
public __nextProps
// 狀態
public __prevState
public state = {}
public __nextState
// 儲存當前組件渲染出的 VNode
public __vnode
// 儲存 組件節點
public __component
constructor(props) {
// 初始化參數
this.props = props
}
public render(): any { return null }
public __createVNode() {
// 該方法用於封裝從新執行 render 的邏輯
// state 與 props 狀態變動的邏輯
this.__prevProps = this.props
this.__prevState = this.state
this.props = this.__nextProps
this.state = this.__nextState
this.__nextState = this.__nextProps = undefined
// 從新執行 render 生成 VNode
this.__vnode = this.render()
return this.__vnode
}
}
複製代碼
有了這個類後,咱們就可使用 React
的方式繼承出 自定義組件 進行渲染:
class App extends Component {
constructor(props) {
super(props)
this.state = {
content: 'ReactWebGL Component, Hello World',
}
}
public render() {
return (
<Container name="parent"> {this.state.content} </Container>
)
}
}
render(<App />, game.stage) 複製代碼
因爲咱們以前提過,JSX
是以 變量傳遞 的形式編譯的。所以將 <App />
轉換成 VNode
後,其 vnode.type
的值即是 App
這個類,並非相似 div
那樣的 字符串標籤名。因此接下來咱們須要實現一個 組件初始化 的函數(Component
)。這個函數的主要功能是:
實例化組件並獲取組件 render
方法中返回的 元素節點。
// 渲染組件
function renderComponent(compVNode) {
const { type: Comp, props } = compVNode
let instance = compVNode.instance
// 當 instance 已經存在時,則不須要從新建立實例
if (!instance) {
// 傳入 props,初始化組件實例
// 支持 類組件 或者 函數組件
if (Comp.prototype && Comp.prototype.render) {
instance = new Comp(props)
} else {
instance = new Component(props)
instance.constructor = Comp
instance.render = () => instance.constructor(props)
}
// 初次渲染時
// 未來的屬性與狀態其實即是與當前值一致
instance.__nextProps = props
instance.__nextState = instance.state
}
// 調用 render 獲取 VNode
const vnode = instance.__createVNode()
// 組件、元素、實例之間保持相互引用,有利於雙向鏈接整棵虛擬樹
instance.__component = compVNode
compVNode.instance = instance
compVNode.vnode = vnode
vnode.component = compVNode
return vnode
}
複製代碼
接下來咱們只須要在 createElm
的函數加入: 當傳入的爲 組件節點 時調用函數初始化生成 元素節點,後續只須要繼續原有的邏輯繼續建立,便能正確渲染組件。
function createElm(vnode) {
// 當爲組件時,初始化組件
// 從新賦值成 元素節點
if (typeof vnode.type === 'function') {
vnode = renderComponent(vnode)
}
// 維持原有邏輯
...
return vnode.elm
}
複製代碼
保存執行,頁面中已經能正確的渲染出 <App>
組件了。延續以前的邏輯,在完成初次渲染後,接下來就是組件的更新。這就是咱們最常使用的 this.setState
了。
在上一篇文章中,咱們實現了 虛擬DOM 更新函數 diff
。參數爲 新舊虛擬節點(oldVNode
、newVNode
)。因此組件更新的原理也同樣:
獲取組件實例前後渲染的新舊 VNode
再觸發 diff
函數。
在剛纔 Component
的渲染中,咱們已經把 render
生成的 VNode
保存在 this.__vnode
上,這即是初始化時生成的 舊虛擬節點(oldVNode
)。因此咱們要作的就是:
setState
中 更新狀態,調用 render
生成 新虛擬節點(newVNode
),觸發 diff
更新。
所以咱們在類上新增兩個方法: setState
和 __update
:
class Component {
// 其他方法
...
// 更新函數
public __update = () => {
// 臨時存儲 舊虛擬節點 (oldVNode)
const oldVNode = this.__vnode
this.__nextProps = this.props
if (!this.__nextState) this.__nextState = this.state
// 從新生成 新虛擬節點(newVNode)
this.__vnode = this.__createVNode()
// 調用 diff,更新 VNode
diff(oldVNode, this.__vnode)
}
// 更新狀態
public setState(partialState, callback?) {
// 合併狀態, 暫存於 即將更新態 中
if (typeof partialState === 'function') {
partialState = partialState(this.state)
}
this.__nextState = {
...this.state,
...partialState,
}
// 調用更新,並執行回調
this.__update()
callback && callback()
}
}
複製代碼
到這裏咱們能夠看出: setState
封裝了 diff
方法。但因爲 diff
的複雜度,性能的優化會是一個咱們須要着重考慮的點。每執行一次 setState
,就須要從新生成 newVNode
進行 diff
。所以,當組件很是複雜時或者連續更新時,可能會致使 主進程的阻塞,形成頁面的卡死。
這裏咱們須要有兩個優化:
setState
異步化,避免阻塞主進程;
setState
合併,屢次連續調用會被最終合併成一次;
同一組件 連續屢次更新;
父子級連續觸發更新,因爲 父級更新其實已經包含子級的更新,此時若是子級再自我更新一次,則就變成了一種無謂消耗;
爲了這個優化,咱們首先須要一個更新隊列功能:
能夠異步化調用更新;
爲組件標註,保證一次循環中單個組件只會更新一次;
咱們先來實現個 異步執行隊列:
// 更新異步化,採用屬於微任務的 Promise,兼容性使用 setTimeout
// 這裏使用微任務,能夠保證宏任務的優先執行
// 保證例如 UI渲染 等更爲重要的任務,避免頁面卡頓;
const defer = typeof Promise === 'function'
? Promise.prototype.then.bind(Promise.resolve())
: setTimeout
// 更新隊列
const updateQueue: any[] = []
// 隊列更新 API
export function enqueueRender(updater) {
// 將全部 updater 同步推入更新隊列中
// 爲實例添加一個屬性 __dirty,標識是否處於待更新狀態
// 初始 和 更新完畢,該值會被置爲 false
// 推入隊列時,標記爲 true
if (
!updater.__dirty &&
(updater.__dirty = true) &&
updateQueue.push(updater) === 1
) {
// 異步化沖洗隊列
// 最終只執行一次沖洗
defer(flushRenderQueue)
}
}
// 合併一次循環中屢次 updater
function flushRenderQueue() {
if (updateQueue.length) {
// 排序更新隊列
updateQueue.sort()
// 循環隊列出棧
let curUpdater = updateQueue.pop()
while (curUpdater) {
// 當組件處於 待更新態 時,觸發組件更新
// 若是該組件已經被更新完畢,則該狀態爲 false
// 則後續的更新均再也不執行
if (curUpdater.__dirty) {
// 調用組件自身的更新函數
curUpdater.__update()
// 執行 callback
flushCallback(curUpdater)
}
curUpdater = updateQueue.pop()
}
}
}
// 執行緩存在 updater.__setStateCallbacks 上的回調
function flushCallback(updater) {
const callbacks = updater.__setStateCallbacks
let cbk
if (callbacks && callbacks.length) {
while (cbk = callbacks.shift()) cbk.call(updater)
}
}
複製代碼
完成這個方法後,咱們來修改下上面的 setState
函數:
class Component {
// 其他方法
...
public setState(partialState = {}, callback?) {
// 合併狀態, 暫存於 即將更新態 中
// 處理參數爲函數的形式
if (typeof partialState === 'function') {
partialState = partialState(this.state, this.props)
}
this.__nextState = {
...this.state,
...partialState,
}
// 緩存回調
callback && this.__setStateCallbacks.push(callback)
// 把組件自身先推入更新隊列
enqueueUpdate(this)
}
}
複製代碼
此時,因爲 更新隊列 爲異步的,所以當屢次連續調用 setState
時,組件的狀態會被 同步合併,待所有完成後,纔會進入更新隊列的沖洗並最終只執行一次組件更新。
React 中組件還有個更新的方法: forceUpdate(callback)
,該方法的功能實際上是與 this.setState({}, callback)
相同的,惟一有個須要注意的點就是: 觸發的更新不須要通過 shouldComponentUpdate
的判斷,實現只須要加個標識位便可。
回到性能優化這個點,從這裏的簡單實現咱們能夠看出: 雖然異步化了更新流程,但本質上仍然沒有解決 複雜的組件 diff
帶來長時間執行阻塞主進程。我記得之前文章有說過: 最有效的性能優化方式就是 異步、任務分割 和 緩存策略。
經過把同步的代碼執行變成異步,把串行變成並行,能夠有效提升 執行的時間利用率 和 保證代碼優先級。從這裏能夠延伸出兩種優化方向:
diff
自己就較爲複雜,還要須要處理好主進程與線程之間的交互,會致使複雜度極高,但也並不是不可行,後續也許是個優化方向。將本來會阻塞主進程的 大塊邏輯執行進行拆解,分割成一個個小任務。從而能夠在邏輯中找到合適的時機點 分段執行,即 不會阻塞主進程,又可讓代碼快速高效的執行,最大化利用物理資源。
Facebook 的大神們選擇了這條優化方向,這就是 React 16 新引入的 Fiber
理念的最主要目的。上面咱們實現的 diff
中,有着一個很大的障礙:
一棵完整 虛擬DOM樹 更新,必須一次性更新完成,中間沒法被暫停,也沒法被分割。
而 Fiber
最主要的功能就是 指針映射,保存上一個更新的組件與下一步須要更新的組件,從而完成 可暫停可重啓。計算進程的運行時間,利用瀏覽器的 requestIdleCallback
與 requestAnimationFrame
接口,當有優先級更高的任務時,優先執行,暫停下一個組件的更新。待空閒時再重啓更新。
Fiber
算是一種編程思想,在其它語言中也有許多應用(Ruby Fiber
)。核心思想是:
任務拆分和協同,主動把執行權交給主線程,使主線程有時間空擋處理其餘高優先級任務。
但實現複雜度較高,爲了本文便於理解,暫時並無引入。等之後有機會咱們再來一塊兒深挖 Fiber
的實現,也許能成爲更多使用場景的性能優化手段。
在上篇文章中,咱們優先實現了 diffVNode
方法用於更新 元素節點,但組件節點的更新與元素節點的更新是不一樣的。當出現組件嵌套的狀況時,咱們就須要一個新的方法(diffComponent
)用於組件節點的更新。
與 元素節點 不一樣,組件節點之間的更新重要的是重渲染,相似於咱們上面的 setState
。
複用已建立好的組件實例,根據新的 狀態(state
)與 屬性(props
) 從新執行 render
生成 元素節點,再遞歸比對,
也就是說,咱們須要在 diffVNode
外圍再作一層判斷處理:
function diff(oldVNode, newVNode) {
if (isSameVNode(oldVNode, newVNode)) {
if (typeof oldVNode.type === 'function') {
// 組件節點
diffComponent(oldVNode, newVNode)
} else {
// 元素節點,
// 直接執行比對
diffVNode(oldVNode, newVNode)
}
} else {
// 新節點替換舊節點
...
}
}
// 組件比對
function diffComponent(oldCompVNode, newCompVNode) {
const { instance, vnode: oldVNode, elm } = oldCompVNode
const { props: nextProps } = newCompVNode
if (instance && oldVNode) {
instance.__dirty = false
// 更新狀態和屬性
instance.__nextProps = nextProps
if (!instance.__nextState) instance.__nextState = instance.state
// 複用舊組件實例和元素
newCompVNode.instance = instance
newCompVNode.elm = elm
// 使用新屬性、新狀態,舊組件實例
// 從新生成 新虛擬DOM
const newVNode = initComponent(newCompVNode)
// 遞歸觸發 diff
diff(oldVNode, newVNode)
}
}
複製代碼
組件有一個至關重要的特徵,即是具備 生命週期。不一樣的函數鉤子對應了組件 從初始化到銷燬 的各個關鍵時間點。主要是爲了讓業務方有能力 插入組件的渲染工做流 中,編寫業務邏輯。咱們先來簡單梳理下最新 React 組件的生命週期:
constructor
state
;static getDerivedStateFromProps(nextProps, prevState)
在組件的模板渲染中,咱們一般使用的數據爲 state
和 props
,而 props
由父級傳入,組件自己並沒有法直接修改,所以惟一的常見需求就是: 根據父級傳入的 props
動態修改 state
。該生命週期就是爲此而生;
你們可能會有疑問: 該方法爲何爲靜態方法? 而不是常規的實例方法呢?
render()
state
和 props
,生成 虛擬DOM;componentDidMount()
static getDerivedStateFromProps(nextProps, prevState)
shouldComponentUpdate(nextProps, nextState)
上篇文章中的 diff
優化策略中有提到: 爲了減小 無謂的更新消耗,賦予組件一個能夠 主動中斷更新流 的 API
。根據參數中的 更新屬性 和 更新狀態,業務方自行判斷是否須要繼續往下執行 diff
,從而能有效地提高 更新性能;
你們記得 React 中有種組件叫 純組件(PureComponent
) 吧,其實這個類繼承於普通的 Component
上封裝的,能夠減小多餘的 render
,提高性能。
默認使用 shouldComponentUpdate
函數設定更新條件: 僅當 props
和 state
發生改變時,纔會觸發更新。這裏使用了 Object
淺層比對,也就是僅作第一層比對,即 1. key 是否徹底匹配;2. value 是否全等; 因此若是須要超過一層的數據變更,純組件即沒法正確更新了;
這也是爲何 React 提倡使用 不變數據 的原理,能有效地使用淺層比對;
不變數據: 提倡數據不可變,任何修改都須要返回一個新的對象,不能直接修改原對象,這樣能有效提升比對效率,減小無謂性能損耗。
render()
getSnapshotBeforeUpdate(prevProps, prevState)
替換舊版的 componentWillUpdate
,觸發時機點: 在數據狀態已更新,最新 VNode
已生成,但 真實元素還未被更新;
能夠用來在 更新以前 從真實元素或狀態中獲取計算一些信息,便於在更新後使用;
componentDidUpdate(prevProps, prevState, snapshot)
組件更新完成後調用;
能夠用於 監聽數據變化,使用 setState
時必須加條件,避免無限循環;
componentWillUnmount()
咱們也按這樣的目標來在咱們的 Component
上實現這些生命週期,那如何更好的組織生命週期呢?這裏我考慮到的是:
組件做爲元素的容器,生命週期的本質實際上是 其渲染出的元素節點的生命週期。
也就是說,關鍵點仍是在於 元素在視圖中的工做流,什麼時候被掛載 - 更新 - 卸載。因此爲了更好的維護性和拓展性,更理想的方式應該是 爲元素節點統一添加生命週期,而不是單獨爲組件,這樣可大大下降複雜度,增長可拓展性。
那咱們第一步先根據上面須要的生命週期來理一下須要哪些時機:
create
);insert
);willupdate
);update
);willremove
);原理就很簡單了,只須要在 VNode
工做流中的對應時期調用相應的生命週期函數便可。那咱們如今 VNode
上新增一個屬性 hooks
,用於 儲存 對應的生命週期函數:
interface VNode {
...
hooks: {
create?: (vnode) => void
insert?: (vnode) => void
willupdate?: (oldVNode, newVNode) => void
update?: (oldVNode, newVNode) => void
willremove?: (vnode) => void
}
}
複製代碼
新增一個觸發的方法(fireVNodeHook
):
function fireVNodeHook(vnode, name, ...data) {
// 根據 生命週期名稱
// 執行儲存在 VNode 上的對應函數便可
const { hooks: _hooks } = vnode
if (_hooks) {
hook = _hooks[name]
hook && hook(...data)
}
}
複製代碼
有了這層基礎方法後,咱們只須要分別在以前所寫的 渲染與更新 流程中的各個函數適時地觸發就好了。
create
該時機是在 元素被建立後,但還未被掛載以前。因爲咱們以前將邏輯統一收歸爲 createElm
,所以只須要在該函數末尾統一加入觸發便可。
function createElm(vnode) {
// 建立元素邏輯
...
// 觸發 虛擬DOM 上儲存的 鉤子函數
fireVNodeHook(vnode, 'create', vnode)
return vnode.elm
}
複製代碼
insert
元素被掛載到視圖 上的時機。從元素的角度來看,就是被 append
到父級中的時機點。這個時機點比較分散,但也比較好加入,找到咱們使用 Api
中的 append
方法加入,總共有三個地方:
render
函數中加入對 根節點 的觸發;createElm
函數中加入對 全部子級 的觸發;diffChildren
列表比對中 新增列表項 的觸發;willupdate
與 update
更新以前 與 更新以後,對應的即是咱們的 diff
函數。因爲最終均需走到 diffVNode
中,所以只須要在 diffVNode
開頭和末尾觸發便可。
willremove
元素被卸載時,其實與 insert
相似,只須要關注 Api
中 removeChild
的調用時機便可。在 diff
列表比對期間,當新列表中不存在時,咱們須要刪除舊列表中的元素,也就是以前寫的業務函數 removeVNodes
。
因爲 元素節點 纔是貫穿整棵 虛擬DOM 渲染與更新的關鍵,所以咱們上面先實現的是對 元素節點 的生命週期觸發。可是咱們最終須要是 組件節點 的生命週期。因爲 組件節點 與 元素節點 爲一一對應的 上下層級關係,所以這裏咱們還須要作一層轉接:
把組件節點的生命週期賦值給其生成的元素節點。
首先咱們先來爲組件定義上生命週期,並定義一箇中轉對象 __hooks
,實現 組件節點週期與元素節點週期的轉換:
class Component {
public __hooks = {
// 元素節點 插入時機,觸發 didMount
insert: () => this.componentDidMount(),
// 元素節點 更新以前,觸發 getSnapshotBeforeUpdate
willupdate: (vnode) => {
this.__snapshot = this.getSnapshotBeforeUpdate(this.__prevProps, this.__prevState)
},
// 元素節點 更新以後, 觸發 didUpdate
update: (oldVNode, vnode) => {
this.componentDidUpdate(this.__prevProps, this.__prevState, this.__snapshot)
this.__snapshot = undefined
},
// 元素節點 卸載以前, 觸發 willUnmount
willremove: (vnode) => this.componentWillUnmount(),
}
// 默認生命週期函數
// getDerivedStateFromProps(nextProps, state)
public getSnapshotBeforeUpdate(prevProps, prevState) { return undefined }
public shouldComponentUpdate(nextProps, nextState) { return true }
public componentDidMount() { }
public componentDidUpdate(prevProps, prevState, snapshot) { }
public componentWillUnmount() { }
}
複製代碼
而後咱們只須要在 __createVNode
方法中將 this.__hooks
賦值給生成出的 VNode
便可:
class Component {
...
public __createVNode() {
// ...
this.__vnode = this.render()
// 賦值給對應的 元素節點,
// 實現該 元素節點 與 組件 之間生命週期的綁定
this.__vnode.hooks = this.__hooks
return this.__vnode
}
}
複製代碼
最後,你們可能發現咱們還有兩個鉤子沒有實現: getDerivedStateFromProps
和 shouldComponentUpdate
,這是由於這兩個生命週期會影響到更新結果,所以須要 深刻到更新流程中,沒法單純的經過 元素節點 的生命週期來實現。
但其實也很簡單,就是在 更新以前,須要根據這兩個函數的返回結果,適當調整下更新邏輯便可:
// Componet 中的 __update 方法
class Component {
// ...
public __update = () => {
// 臨時存儲 舊虛擬節點 (oldVNode)
const oldVNode = this.__vnode
this.__nextProps = this.props
if (!this.__nextState) this.__nextState = this.state
// 執行 getDerivedStateFromProps
// 更新 state 狀態
const cls = this.constructor
if (cls.getDerivedStateFromProps) {
const state = cls.getDerivedStateFromProps(this.__nextProps, this.state)
if (state) {
this.__nextState = Object.assign(this.__nextState, state)
}
}
// 在 diff 以前調用 shouldComponentUpdate 進行判斷
// true: 生成新 VNode,繼續 diff
// false: 清空狀態
if (this.shouldComponentUpdate(this.props, this.__nextState)) {
// 從新生成 新虛擬節點(newVNode)
this.__vnode = this.__createVNode()
// 調用 diff,更新 VNode
diff(oldVNode, this.__vnode)
} else {
// 清空狀態更新
this.__nextProps = this.__nextState = undefined
}
// 剛纔 異步更新隊列 中標識的組件 待更新狀態
// 在更新完後置爲 false
this.__dirty = false
}
}
複製代碼
組件更新還有另一個地方,即 diffComponent
,也須要加入上述相似的執行和判斷。完成這部分代碼後,咱們來簡單測試個 DEMO:
<App>
、<BBB txt={this.state.txt} />
正確渲染;<App> setState
,<BBB>
文字元素正確更新;圖1. 生命週期演示DEMO
在這系列文章中,咱們實現了 React 最核心的部分: JSX、組件、渲染、更新。咱們基於 動手實踐 的方式,按部就班地探討了一些原理與策略,得出一些最佳實踐。相信走完這遍旅程後,你們能對 React 有了更深層次的瞭解,可以給到各位小夥伴啓發與幫助。其實我也同樣,也是在這個旅程中跟你們一塊兒共同窗習,共同成長。
還有許多模塊,如 Context
、Refs
、Fragment
和 一些全局API,如 cloneElement
等,還有代碼中一些更嚴謹的判斷及邊界狀況的處理,並無在文章中體現,主要是因爲這些部分更多的是純邏輯的擴展,同時也是爲了便於理解。若是童鞋們有興趣,能夠到 github 中查看完整版代碼:
另外我也想稍微嘮嗑下關於 React-WebGL 這個想法。
近階段我接觸了一些 Web 遊戲的開發,有了一些從前端開發者出發的思考與理解。在遊戲開發領域,傳統的遊戲開發者有着一套與前端領域徹底不一樣的思惟編程模式。隨着 Web 的發展,使他們須要拓展到 Js 的環境中。因此出現了一系列的遊戲引擎庫,本質上是從其它平臺的庫移植過來的。當我從一個前端開發者的角度在進行開發時,其實並非說入門難,學習成本高,而是給個人感受是: 相似用純原生 js 在寫頁面,以爲效率低下。因此這也是 React-WebGL 的出發點,指望能將如今 Web 中更優秀的理念運用到遊戲開發,甚至找到一種更高效的開發模式,提高效率,完善生態。
固然,這僅僅只是一個起點, 遊戲開發 與 界面開發 確實有着許多異同點,如何找到一種更現代化更高效的 Web 遊戲開發模式,這還須要很長的一段旅程。我也一直在思考,一直在摸索,相信能有一些好玩的東西。沒有嘗試,沒有努力,就千萬別在起點就放棄了。有什麼問題,有什麼想法,直接找我一塊兒探討哈。🙃~~
Tips:
看博主寫得這麼辛苦下,跪求點贊、關注、Star!更多文章猛戳 ->
郵箱: 159042708@qq.com 微信/QQ: 159042708