這個庫能做什么?
從這個庫的API上來看,這些狀態(tài)都保存在組件中。沒有一個初始化(異步)的入口。一個關于這個庫最簡單的應用例子(在所有將render props中都會說的):
在沒有render props時,是這樣寫:
class App extends React.Component {
state = { visible: false };
showModal = () => {
this.setState({
visible: true
});
};
handleOk = e => {
this.setState({
visible: false
});
};
handleCancel = e => {
this.setState({
visible: false
});
};
render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
如果用render props會這樣寫
class App extends React.Component {
render() {
return (
<Toggle initial={false}>
{({ on, toggle }) => (
<Button type="primary" onClick={toggle}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={on}
onOk={toggle}
onCancel={toggle}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
)}
</Toggle>
);
}
}
ReactDOM.render(<App />, mountNode);
這個庫的API和代碼組織我個人覺得非常棒
源碼解析
Compose組件
compose方法是這個庫的核心方法。用來組合render props的組件。我現(xiàn)在只能結(jié)合react的渲染原理,執(zhí)行這段代碼,搞清楚整個流程。反正看完之后,我覺得能作者寫出這種代碼真的很牛逼
ps: 市面上所謂的react-powerplug的compose的解讀都是shit。因為它們的compose的源碼解析只有這樣一句話

雖然你也挑不出錯來,可這說了和沒說有啥差別????
react的渲染原理
這里以react最初的版本進行原理分析。react的渲染流程兩個關鍵的點: 組件的jsx轉(zhuǎn)化以及RenderDom.render函數(shù)。
- babel會將
JSX
class App extends Component{
render(){
return <Hello></Hello>
}
}
轉(zhuǎn)化為這個樣子:
var Hello = function Hello() {
return _react2.default.createElement(World, null);
};
這里需要補充一下createElement和cloneElement的三個參數(shù)如下所示
createElement(element, props,children)//第三個即為子組件
createElement以及cloneElement會返回一個element對象,element對象一般結(jié)構(gòu)如下
{
type : 'div',
props: {
className : 'cn',
children : [
{
type : function Header,
props : {
children : 'hello'
}
},
{
type : 'div',
props : {
children : 'start'
}
},
'Right Reserve'
]
}
}

element的type這樣幾種類型: function(自定義組件), string(原生的dom)之類。
-
ReactDom.render函數(shù)
一般對于react的應用,都會有這種代碼
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.js'
ReactDOM.render(<App />, document.getElementById('root'))
其中ReactDOM.render函數(shù)流程是這個樣子
const rootID = 0
const mainComponent = instantiateReactComponent(element)
const containerContent = mainComponent.mountComponent(rootID)
instantiateReactComponent根據(jù)element類型,生成不同的class。這些class都會實現(xiàn)同樣的方法mountComponent,看代碼
export function instantiateReactComponent(element) {
let instance = null
if (element === null || element === false) {
instance = new ReactDOMEmptyComponent()
}
if (typeof element === 'string' || typeof element === 'number') {
instance = new ReactDOMTextComponent(element)
}
if (typeof element === 'object') {
let type = element.type
if (typeof type === 'string') {
instance = new ReactDomComponent(element)
} else if (typeof type === 'function'){
instance = new ReactCompositeComponent(element)
}
}
return instance
}
上面根據(jù)element類型生成不同的類。需要關注的是其中兩個類:ReactDomComponent和ReactCompositeComponent。
其中ReactDomComponent表示原生組件,即瀏覽器支持的標簽(div, p, h1, etc.)。它的代碼如下所示:
class ReactDomComponent {
constructor(element) {
let tag = element.type
this._element = element
this._tag = tag.toLowerCase()
this._rootID = 0
}
mountComponent(rootID) {
this._rootID = rootID
if (typeof this._element.type !== 'string') {
throw new Error('DOMComponent\'s Element.type must be string')
}
let ret = `<${this._tag} `
let props = this._element.props
for (var propsName in props) {
if (propsName === 'children') {
continue
}
let propsValue = props[propsName]
ret += `${propsName}=${propsValue}`
}
ret += '>'
let tagContent = ''
if (props.children) {
tagContent = this._mountChildren(props.children) //重點是這個,如何解析子組件
}
ret += tagContent
ret += `</${this._tag}>`
return ret
}
}
其中_mountChildren的代碼如下所示
let result = ''
for (let index in children) {
const child = children[index]
const childrenComponent = instantiateReactComponent(child) //又來重復整個流程
result += childrenComponent.mountComponent(index)
}
return result
重頭戲來了, ReactCompositeComponent類的代碼呢?
class ReactCompositeComponent {
constructor(element) {
this._element = element
this._rootId = 0
}
mountComponent(rootID) {
this._rootId = rootID
if (typeof this._element.type !== 'function') {
throw new Error('CompositeComponent\'s Element.type must be function')
}
const Component = this._element.type
const props = this._element.props
const instance = new Component(props)
const renderedElement = instance.render() //如果是函數(shù)組件應該是直接調(diào)用函數(shù)方法
const renderedComponent = instantiateReactComponent(renderedElement)
const renderedResult = renderedComponent.mountComponent(rootID)
return renderedResult
}
}
ReactCompositeComponent類。首先初始化類,獲取實例。調(diào)用render方法,返回element對象,然后根據(jù)element對象,轉(zhuǎn)化對應的component類。調(diào)用該類的mountComponent函數(shù)。
總結(jié)下來,react的渲染流程圖如下所示:

基本上我將[React 初始化渲染](https://www.ahonn.me/2017/06/08/write-a-react-from-scratch-init-render/
https://zhuanlan.zhihu.com/p/45091185)這篇文章摘了重點拿出來說。關于react的渲染流程,這篇文章寫得非常詳細了。
搞清楚react的渲染原理,此時可以來結(jié)合compose的源碼,來搞清楚compose代碼執(zhí)行的流程。
compose代碼執(zhí)行流程
先看compose的使用
import { compose, Counter, Toggle } from 'react-powerplug' // note lowercased (c)ompose
// the order matters
const ToggleCounter = compose(
<Counter initial={5} />, // accept a element
Toggle, // or just a component
)
<ToggleCounter>
{(counter, toggle) => {
<div>hello</div>
}}
</ToggleCounter>
再看compose的代碼
const compose = (...elements) => {
const reversedElements = elements.reverse()
return composedProps => {
// Stack children arguments recursively and pass
// it down until the last component that render children
// with these stacked arguments
function stackProps(i, elements, propsList = []) {
const element = elements[i]
const isTheLast = i === 0
// Check if is latest component.
// If is latest then render children,
// Otherwise continue stacking arguments
const renderFn = props =>
isTheLast
? renderProps(composedProps, ...propsList.concat(props))
: stackProps(i - 1, elements, propsList.concat(props))
// Clone a element if it's passed created as <Element initial={} />
// Or create it if passed as just Element
const elementFn = isElement(element)
? React.cloneElement
: React.createElement
return elementFn(element, {}, renderFn)
}
return stackProps(elements.length - 1, reversedElements)
}
}
結(jié)合react的渲染原理來看。
1.從ReactDOM.render(ToggleCounter,)開始
2.因為ToggleCounter是函數(shù)式組件,經(jīng)過instantiateReactComponent函數(shù)后,調(diào)用的是ReactCompositeComponent的mountComponent方法,根據(jù)我上面畫的流程圖
3.初始化ToggleCounter實例并調(diào)用render函數(shù)(函數(shù)式組件,應該直接調(diào)用函數(shù)即可)
//1.調(diào)用ToggleCounter函數(shù)
const ToggleCounter = (composedProps)=> {
return stackProps(elements.length - 1, reversedElements)
}
ToggleCounter((a,b)=> {<div>hello</div>})
//既:
stackProps(Counter,[Toggle,Counter], [])
4.而調(diào)用stackProps函數(shù)結(jié)果是,生成元素B
//元素B
cloneElement(Counter, {}, (props)=> {stackProps(0,[Toggle,Counter], propsList.concat(props))})
4.元素B又是函數(shù)式組件。重復上訴流程。初始化Counter實例并調(diào)用render函數(shù)(函數(shù)式組件調(diào)用函數(shù)本身)。而Counter的函數(shù)本質(zhì)邏輯,是將其內(nèi)部state作為參數(shù)給它的children。既這段代碼
const fn = isFn(children) ? children : render
return fn ? fn(...props) : null
所以執(zhí)行了這段代碼
//1.此時元素B的children如下所示:
const children1 = (props)=> {stackProps(0,[Toggle,Counter], propsList.concat(props))})
//2.執(zhí)行couter函數(shù)
children1(propsCounter) = stackProps(0,[Toggle,Counter], [propsCounter])
//而stackProps返回了新的元素C
React.createElement(Toggle, {},(props)=> {renderProps((a,b)=> {<div>hello</div>}, ...[propsCounter].concat(props))})
5.元素C又是函數(shù)式組件。重復上訴流程。同Counter一樣,執(zhí)行這段代碼
//1.Toggle的子組件children2如下所示
const children2 = (props)=> {renderProps((a,b)=> {React.createElement(div, {}, 'demo')} ...[propsCounter].concat(props)}
//2.Toggle組件調(diào)用render函數(shù)(函數(shù)式組件,直接執(zhí)行本函數(shù))
children2(propsToggle) = renderProps((a,b)=> {<div>hello</div>}, ...[propsCounter,propsToggle ]}
renderProps的函數(shù)的定義
const renderProps = ({ children, render }, ...props) => {
if (process.env.NODE_ENV !== 'production') {
warn(
isFn(children) && isFn(render),
'You are using the children and render props together.\n' +
'This is impossible, therefore, only the children will be used.'
)
}
const fn = isFn(children) ? children : render
return fn ? fn(...props) : null
}
所以,相當于執(zhí)行這個步驟
fn = (a,b)=> {<div>hello</div>}
fn([propsCounter,propsToggle])
獲得瀏覽器支持的標簽div,初始化成ReactDOMComponent進行渲染。結(jié)束。
在看這段代碼的時候,結(jié)合渲染原理。搞清楚了compose的整個流程。
Value組件
Value組件是基礎組件。非常簡單的兩個功能:設置state和重置state。也是使用的render Props的方式
class Value extends Component {
state = {
value: this.props.initial,
}
_set = (updater, cb = noop) => {
const { onChange = noop } = this.props
this.setState(
typeof updater === 'function'
? state => ({ value: updater(state.value) })
: { value: updater },
() => {
onChange(this.state.value)
cb()
}
)
}
_reset = (cb = noop) => {
this._set(this.props.initial, cb)
}
render() {
return renderProps(this.props, {
value: this.state.value,
set: this._set,
reset: this._reset,
})
}
}
export default Value
這個代碼太簡單了。不需要怎么說。renderProps是一個基本方法:
const renderProps = ({ children, render }, ...props) => {
if (process.env.NODE_ENV !== 'production') {
warn(
isFn(children) && isFn(render),
'You are using the children and render props together.\n' +
'This is impossible, therefore, only the children will be used.'
)
}
const fn = isFn(children) ? children : render
return fn ? fn(...props) : null
}
通過判斷children是否
List組件
const complement = fn => (...args) => !fn(...args)
const List = ({ initial = [], onChange, ...props }) => (
<Value initial={initial} onChange={onChange}>
{({ value, set, reset }) =>
renderProps(props, {
list: value,
first: () => value[0],
last: () => value[Math.max(0, value.length - 1)],
set: list => set(list),
push: (...values) => set(list => [...list, ...values]),
pull: predicate => set(list => list.filter(complement(predicate))),
sort: compareFn => set(list => [...list].sort(compareFn)),
reset,
})
}
</Value>
)
只是對Value組件再來了一層包裝,包裝成更符合List組件的API.
hover組件
hover這類狀態(tài)組件,更簡單了。加了一些綁定事件設置值。
const Hover = ({ onChange, ...props }) => (
<Value initial={false} onChange={onChange}>
{({ value, set }) =>
renderProps(props, {
hovered: value,
bind: {
onMouseEnter: () => set(true),
onMouseLeave: () => set(false),
},
})
}
</Value>
)
參考:
react 的源碼解析:
https://juejin.im/post/5b9a45fc5188255c402af11f
react的渲染原理
https://www.ahonn.me/2017/06/08/write-a-react-from-scratch-init-render/
https://github.com/creeperyang/blog/issues/30