真正的react-powerplug的源码解读

这个库能做什么?

从这个库的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的源码解析只有这样一句话

image.png

虽然你也挑不出错来,可这说了和没说有啥差别????

react的渲染原理

这里以react最初的版本进行原理分析。react的渲染流程两个关键的点: 组件的jsx转化以及RenderDom.render函数。

  • babel会将 JSX

class App extends Component{

    render(){
        return 
    }
}

转化为这个样子:

var Hello = function Hello() {
    return _react2.default.createElement(World, null);
};

这里需要补充一下createElementcloneElement的三个参数如下所示

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'
    ]
  }
}
image.png

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类型生成不同的类。需要关注的是其中两个类:ReactDomComponentReactCompositeComponent

其中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 += ``
    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的渲染流程图如下所示:

image.png

基本上我将[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

你可能感兴趣的:(真正的react-powerplug的源码解读)