这个库能做什么?
从这个库的API上来看,这些状态都保存在组件中。没有一个初始化(异步)的入口。一个关于这个库最简单的应用例子(在所有将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 (
Some contents...
Some contents...
Some contents...
);
}
}
ReactDOM.render( , mountNode);
如果用render props会这样写
class App extends React.Component {
render() {
return (
{({ on, toggle }) => (
Some contents...
Some contents...
Some contents...
)}
);
}
}
ReactDOM.render( , mountNode);
这个库的API和代码组织我个人觉得非常棒
源码解析
Compose组件
compose方法是这个库的核心方法。用来组合render props
的组件。我现在只能结合react的渲染原理,执行这段代码,搞清楚整个流程。反正看完之后,我觉得能作者写出这种代码真的很牛逼
ps: 市面上所谓的react-powerplug的compose的解读都是shit。因为它们的compose
的源码解析只有这样一句话
虽然你也挑不出错来,可这说了和没说有啥差别????
react的渲染原理
这里以react
最初的版本进行原理分析。react的渲染流程两个关键的点: 组件的jsx转化以及RenderDom.render
函数。
- babel会将
JSX
class App extends Component{
render(){
return
}
}
转化为这个样子:
var Hello = function Hello() {
return _react2.default.createElement(World, null);
};
这里需要补充一下createElement
和cloneElement
的三个参数如下所示
createElement(element, props,children)//第三个即为子组件
createElement
以及cloneElement
会返回一个element
对象,element
对象一般结构如下
{
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
函数
一般对于react
的应用,都会有这种代码
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.js'
ReactDOM.render( , document.getElementById('root'))
其中ReactDOM.render
函数流程是这个样子
const rootID = 0
const mainComponent = instantiateReactComponent(element)
const containerContent = mainComponent.mountComponent(rootID)
instantiateReactComponent
根据element类型,生成不同的class。这些class都会实现同样的方法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
}
上面根据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() //如果是函数组件应该是直接调用函数方法
const renderedComponent = instantiateReactComponent(renderedElement)
const renderedResult = renderedComponent.mountComponent(rootID)
return renderedResult
}
}
ReactCompositeComponent
类。首先初始化类,获取实例。调用render
方法,返回element
对象,然后根据element
对象,转化对应的component
类。调用该类的mountComponent
函数。
总结下来,react
的渲染流程图如下所示:
基本上我将[React 初始化渲染](https://www.ahonn.me/2017/06/08/write-a-react-from-scratch-init-render/
https://zhuanlan.zhihu.com/p/45091185)这篇文章摘了重点拿出来说。关于react的渲染流程,这篇文章写得非常详细了。
搞清楚react的渲染原理,此时可以来结合compose
的源码,来搞清楚compose
代码执行的流程。
compose代码执行流程
先看compose
的使用
import { compose, Counter, Toggle } from 'react-powerplug' // note lowercased (c)ompose
// the order matters
const ToggleCounter = compose(
, // accept a element
Toggle, // or just a component
)
{(counter, toggle) => {
hello
}}
再看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
// 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)
}
}
结合react的渲染原理来看。
1.从ReactDOM.render(ToggleCounter,)
开始
2.因为ToggleCounter
是函数式组件,经过instantiateReactComponent
函数后,调用的是ReactCompositeComponent的mountComponent
方法,根据我上面画的流程图
3.初始化ToggleCounter实例并调用render函数(函数式组件,应该直接调用函数即可)
//1.调用ToggleCounter函数
const ToggleCounter = (composedProps)=> {
return stackProps(elements.length - 1, reversedElements)
}
ToggleCounter((a,b)=> {hello})
//既:
stackProps(Counter,[Toggle,Counter], [])
4.而调用stackProps函数结果是,生成元素B
//元素B
cloneElement(Counter, {}, (props)=> {stackProps(0,[Toggle,Counter], propsList.concat(props))})
4.元素B又是函数式组件。重复上诉流程。初始化Counter实例并调用render函数(函数式组件调用函数本身)。而Counter的函数本质逻辑,是将其内部state作为参数给它的children。既这段代码
const fn = isFn(children) ? children : render
return fn ? fn(...props) : null
所以执行了这段代码
//1.此时元素B的children如下所示:
const children1 = (props)=> {stackProps(0,[Toggle,Counter], propsList.concat(props))})
//2.执行couter函数
children1(propsCounter) = stackProps(0,[Toggle,Counter], [propsCounter])
//而stackProps返回了新的元素C
React.createElement(Toggle, {},(props)=> {renderProps((a,b)=> {hello}, ...[propsCounter].concat(props))})
5.元素C又是函数式组件。重复上诉流程。同Counter一样,执行这段代码
//1.Toggle的子组件children2如下所示
const children2 = (props)=> {renderProps((a,b)=> {React.createElement(div, {}, 'demo')} ...[propsCounter].concat(props)}
//2.Toggle组件调用render函数(函数式组件,直接执行本函数)
children2(propsToggle) = renderProps((a,b)=> {hello}, ...[propsCounter,propsToggle ]}
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
}
所以,相当于执行这个步骤
fn = (a,b)=> {hello}
fn([propsCounter,propsToggle])
获得浏览器支持的标签div,初始化成ReactDOMComponent
进行渲染。结束。
在看这段代码的时候,结合渲染原理。搞清楚了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, 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
组件再来了一层包装,包装成更符合List
组件的API.
hover组件
hover
这类状态组件,更简单了。加了一些绑定事件设置值。
const Hover = ({ onChange, ...props }) => (
{({ value, set }) =>
renderProps(props, {
hovered: value,
bind: {
onMouseEnter: () => set(true),
onMouseLeave: () => set(false),
},
})
}
)
参考:
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
https://github.com/dt-fe/weekly/blob/master/75.%E7%B2%BE%E8%AF%BB%E3%80%8AEpitath%20%E6%BA%90%E7%A0%81%20-%20renderProps%20%E6%96%B0%E7%94%A8%E6%B3%95%E3%80%8B.md
https://juejin.im/post/5b9a45fc5188255c402af11f