看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/...)

  • 从 0 到 1 实现 React 系列 —— JSX 和 Virtual DOM
  • 从 0 到 1 实现 React 系列 —— 组件和 state|props
  • 从 0 到 1 实现 React 系列 —— 生命周期和 diff 算法
  • 从 0 到 1 实现 React 系列 —— 优化 setState 和 ref 的实现

项目地址

组件即函数

在上一篇 JSX 和 Virtual DOM 中,解释了 JSX 渲染到界面的过程并实现了相应代码,代码调用如下所示:

import React from 'react'
import ReactDOM from 'react-dom'const element = (<div className="title">hello<span className="content">world!</span></div>
)ReactDOM.render(element,document.getElementById('root')
)

本小节,我们接着探究组件渲染到界面的过程。在此我们引入组件的概念,组件本质上就是一个函数,如下就是一段标准组件代码:

import React from 'react'// 写法 1:
class A {render() {return <div>I'm componentA</div>}
}// 写法 2:无状态组件
const A = () => <div>I'm componentA</div>ReactDOM.render(<A />, document.body)

<A name="componentA" /> 是 JSX 的写法,和上一篇同理,babel 将其转化为 React.createElement() 的形式,转化结果如下所示:

React.createElement(A, null)

可以看到当 JSX 中是自定义组件的时候,createElement 后接的第一个参数变为了函数,在 repl 打印 <A name="componentA" />,结果如下:

{attributes: undefined,children: [],key: undefined,nodeName: ƒ A()
}

注意这时返回的 Virtual DOM 中的 nodeName 也变为了函数。根据这些线索,我们对之前的 render 函数进行改造。

function render(vdom, container) {if (_.isFunction(vdom.nodeName)) { // 如果 JSX 中是自定义组件let component, returnVdomif (vdom.nodeName.prototype.render) {component = new vdom.nodeName()returnVdom = component.render()} else {returnVdom = vdom.nodeName() // 针对无状态组件:const A = () => <div>I'm componentsA</div>}render(returnVdom, container)return}
}

至此,我们完成了对组件的处理逻辑。

props 和 state 的实现

在上个小节组件 A 中,是没有引入任何属性和状态的,我们希望组件间能进行属性的传递(props)以及组件内能进行状态的记录(state)。

import React, { Component } from 'react'class A extends Component {render() {return <div>I'm {this.props.name}</div>}
}ReactDOM.render(<A name="componentA" />, document.body)

在上面这段代码中,看到 A 函数继承自 Component。我们来构造这个父类 Component,并在其添加 state、props、setState 等属性方法,从而让子类继承到它们。

function Component(props) {this.props = propsthis.state = this.state || {}
}

首先,我们将组件外的 props 传进组件内,修改 render 函数中以下代码:

function render(vdom, container) {if (_.isFunction(vdom.nodeName)) {let component, returnVdomif (vdom.nodeName.prototype.render) {component = new vdom.nodeName(vdom.attributes) // 将组件外的 props 传进组件内returnVdom = component.render()} else {returnVdom = vdom.nodeName(vdom.attributes)   // 处理无状态组件:const A = (props) => <div>I'm {props.name}</div>}...}...
}

实现完组件间 props 的传递后,再来聊聊 state,在 react 中是通过 setState 来完成组件状态的改变的,后续章节会对这个 api(异步)深入探究,这里简单实现如下:

function Component(props) {this.props = propsthis.state = this.state || {}
}Component.prototype.setState = function() {this.state = Object.assign({}, this.state, updateObj) // 这里简单实现,后续篇章会深入探究const returnVdom = this.render() // 重新渲染document.getElementById('root').innerHTML = nullrender(returnVdom, document.getElementById('root'))
}

此时虽然已经实现了 setState 的功能,但是 document.getElementById('root') 节点写死在 setState 中显然不是我们希望的,我们将 dom 节点相关转移到 _render 函数中:

Component.prototype.setState = function(updateObj) {this.state = Object.assign({}, this.state, updateObj)_render(this) // 重新渲染
}

自然地,重构与之相关的 render 函数:

function render(vdom, container) {let componentif (_.isFunction(vdom.nodeName)) {if (vdom.nodeName.prototype.render) {component = new vdom.nodeName(vdom.attributes)} else {component = vdom.nodeName(vdom.attributes) // 处理无状态组件:const A = (props) => <div>I'm {props.name}</div>}}component ? _render(component, container) : _render(vdom, container)
}

在 render 函数中分离出 _render 函数的目的是为了让 setState 函数中也能调用 _render 逻辑。完整 _render 函数如下:

function _render(component, container) {const vdom = component.render ? component.render() : componentif (_.isString(vdom) || _.isNumber(vdom)) {container.innerText = container.innerText + vdomreturn}const dom = document.createElement(vdom.nodeName)for (let attr in vdom.attributes) {setAttribute(dom, attr, vdom.attributes[attr])}vdom.children.forEach(vdomChild => render(vdomChild, dom))if (component.container) {  // 注意:调用 setState 方法时是进入这段逻辑,从而实现我们将 dom 的逻辑与 setState 函数分离的目标;知识点: new 出来的同一个实例component.container.innerHTML = nullcomponent.container.appendChild(dom)return}component.container = containercontainer.appendChild(dom)
}

让我们用下面这个用例跑下写好的 react 吧!

class A extends Component {constructor(props) {super(props)this.state = {count: 1}}click() {this.setState({count: ++this.state.count})}render() {return (<div><button onClick={this.click.bind(this)}>Click Me!</button><div>{this.props.name}:{this.state.count}</div></div>)}
}ReactDOM.render(<A name="count" />,document.getElementById('root')
)

效果图如下:

至此,我们实现了 props 和 state 部分的逻辑。

小结

组件即函数;当 JSX 中是自定义组件时,经过 babel 转化后的 React.createElement(fn, ..) 后中的第一个参数变为了函数,除此之外其它逻辑与 JSX 中为 html 元素的时候相同;

此外我们将 state/props/setState 等 api 封装进了父类 React.Component 中,从而在子类中能调用这些属性和方法。

在下篇,我们会继续实现生命周期机制,如有疏漏,欢迎斧正。

项目地址

转载于:https://www.cnblogs.com/MuYunyun/p/9298089.html

从 0 到 1 实现 React 系列 —— 2.组件和 state|props相关推荐

  1. 从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  2. 从 0 到 1 实现 React 系列 —— 1.JSX 和 Virtual DOM

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  3. 从 0 到 1 实现 React 系列 —— 4.优化setState和ref的实现

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  4. 从 0 到 1 实现 React 系列 —— 组件和 state|props

    阅读源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/...) 组件即函数 在上一篇 JSX 和 Vir ...

  5. 【React系列】状态(State)和生命周期

    在上一篇中写过,组件可以分为函数式组件和类组件,并且更新组件的方法也给出了通过传入ReactDOM.render()方法进行更新.但是这种方式并不能很好地进行封装成独立功能的组件,一些操作会由外部进行 ...

  6. React 系列教程

    React 系列教程:学习原地址 React系列(一) - 邂逅React开发 React系列二 - 核心JSX语法一 React系列二 - 核心JSX语法二 React系列三 - 阶段案例练习 Re ...

  7. React组件的State

    React组件的State 1.正确定义State React把组件看成一个状态机.通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致.组件的任何UI改变,都可以从State的变化 ...

  8. dispatch作用 react_「React系列」手把手带你撸后台系统(Redux与路由鉴权)

    [React系列]手把手带你撸后台系统(Redux与路由鉴权) 来源:https://juejin.im/post/5d9b5ddee51d45781b63b8f7 上一篇我们介绍了系统架构,这一篇将 ...

  9. 16、React系列之 React 路由

    版权声明:本文为博主原创文章,未经博主允许不得转载. PS:转载请注明出处 作者:TigerChain 地址:http://www.jianshu.com/p/b55cf53e633a 本文出自 Ti ...

最新文章

  1. 速度超快!字节跳动开源序列推理引擎LightSeq
  2. 用Python做地图投影 - 多面孔的世界
  3. Android分享功能
  4. VS的几个实用快捷键
  5. 【F3简介】一张图看懂FPGA-F3实例
  6. HDU - 7031 Power Station of Art 思维 + 二分图模型
  7. CDI中的事务异常处理
  8. 前端学习(221):字体属性
  9. excel如何输入毫秒级时间
  10. iOS - OC NSTimeZone 时区
  11. Linux - Yocto: 创建toolchain
  12. 外资公司章程标准范本
  13. H5 HTML 移动端触摸拖拽drag drop 自定义拖拽样式 使用PointerEvent模拟的拖拽方案
  14. java中case怎么用,Java中case使用示例,Javacase使用示例,switch([vari
  15. 斯坦福 机器学习-第一章监督学习
  16. 专访吴军:未来10年,AI的发展方向是应用,不会出现重大的理论突破
  17. 女友的生日礼物能随便嘛?Python小妙招:制作一款出圈九宫格抽奖小程序。
  18. Mathmatica中的Sum命令
  19. 详解input value属性
  20. 光子计算机应用领域,光子计算机离我们还有多远?

热门文章

  1. 神曲背后的故事:算法工程师带你理性解构“蚂蚁呀嘿”
  2. 超详解析Flutter渲染引擎|业务想创新,不了解底层原理怎么行?
  3. Serverless 实战 —— Funcraft + OSS + ROS 进行 CI/CD
  4. 剑网三缘起不赚钱也要为玩家送福利!这就是为了老玩家的情怀吧
  5. 阿里云——ECS——Linux服务器购买流程——超级细致
  6. SDN第二章 Ubuntu系统常用操作命令
  7. PHP面试题:请说明 PHP 中传值与传引用的区别。什么时候传值什么时候传引用?
  8. springboot官网-application.properties文件
  9. 【转】PowerShell入门(五):Cmd命令与PowerShell命令的交互
  10. ios Swift 中文学习手册