看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/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, returnVdom
if (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 = props
this.state = this.state || {}
}

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

function render(vdom, container) {
if (_.isFunction(vdom.nodeName)) {
let component, returnVdom
if (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 = props
this.state = this.state || {}
} Component.prototype.setState = function() {
this.state = Object.assign({}, this.state, updateObj) // 这里简单实现,后续篇章会深入探究
const returnVdom = this.render() // 重新渲染
document.getElementById('root').innerHTML = null
render(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 component
if (_.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() : component
if (_.isString(vdom) || _.isNumber(vdom)) {
container.innerText = container.innerText + vdom
return
}
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 = null
component.container.appendChild(dom)
return
}
component.container = container
container.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 中,从而在子类中能调用这些属性和方法。

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

项目地址

从 0 到 1 实现 React 系列 —— 2.组件和 state|props的更多相关文章

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

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

  2. 从 0 到 1 实现 React 系列 —— 5.PureComponent 实现 && HOC 探幽

    本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/...) ...

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

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

  4. 从 0 到 1 实现 React 系列 —— 3.生命周期和 diff 算法

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

  5. 学习React系列(十)——Render Props

    解决问题:将行为封装,供多个组件使用(在多个组件之间分享某段代码) 组件中的props属性中包含一个"render"属性(该属性为一个返回值为元素的方法),然后在该组件的rende ...

  6. React高阶组件 和 Render Props

    高阶组件 本质 本质是函数,将组件作为接收参数,返回一个新的组件.HOC本身不是React API,是一种基于React组合的特而形成的设计模式. 解决的问题(作用) 一句话概括:功能的复用,减少代码 ...

  7. 说说React组件的State

    说说React组件的State React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态). 正确定义State React把组件看成一个状态机.通过与用户的交 ...

  8. React组件的State

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

  9. React中父子组件数据传递

    Vue.js中父子组件数据传递:Props Down ,  Events Up Angular中父子组件数据传递:Props Down,  Events  Up React中父子组件数据传递:Prop ...

随机推荐

  1. 使用Visual Studio Team Services敏捷规划和项目组合管理(五)——组合管理

    使用Visual Studio Team Services敏捷规划和项目组合管理(五)--组合管理 组合待办事项为产品所有者提供关于几个敏捷特性团队工作的洞察.产品所有者可以将高优先级的目标定义为Ep ...

  2. jar、war、ear

    附加jboss里面的application.xml <?xml version=”1.0″ encoding=”UTF-8″?> <application xmlns="h ...

  3. weblogic报错----Received exception while creating connection for pool "TDMSKD": The Network Adapter could not establish the connection

    <2017-8-16 上午08时58分37秒 CST> <Info> <WebLogicServer> <BEA-000377> <Startin ...

  4. HDFS客户端的权限错误:Permission denied

    报错:Permission denied: user=root, access=WRITE, inode="hadoop":hadoop:supergroup:rwxr-xr-x ...

  5. 高通平台如何使用QPST抓DUMP

    一 :确认手机状态 手机系统死机白屏后,使用USB线 连接手机和计算机.打开计算机设备管理器 ,当其中与手机相关的端口只有DIAG 口 项(9006端口)时,表明手机处于DUMP 模式,可以抓DUMP ...

  6. c/c++ 图的最短路径 Dijkstra(迪杰斯特拉)算法

    c/c++ 图的最短路径 Dijkstra(迪杰斯特拉)算法 图的最短路径的概念: 一位旅客要从城市A到城市B,他希望选择一条途中中转次数最少的路线.假设途中每一站都需要换车,则这个问题反映到图上就是 ...

  7. 编写脚本实现DHCP服务与DHCP中继自动化执行

    编写脚本实现DHCP服务与DHCP中继自动化执行 本脚本是在liunx搭建DHCP服务器以及DHCP中继服务器实验环境下实现的https://www.cnblogs.com/yuzly/p/10539 ...

  8. {windows故障}关于WIN7故障模块StackHash_0a9e解决方法

    问题背景:我给同事重装好系统后,想用驱动精灵(网卡版)给新系统安装驱动,但是在安装驱动精灵的过程中老是出现标题的问题,windows停止工作,导致无法安装,最后看到这两个方法后,把网络适配器禁用,然后 ...

  9. SSL 原理及 https 配置

    目录 1. SSL 原理 1.1. SSL 简介 1.2. 主要概念 1.3. 建立安全连接的过程 2. https 配置 (以 nginx 为例) SSL 原理 SSL 简介 SSL (Secure ...

  10. FCM算法的matlab程序

    FCM算法的matlab程序 在“FCM算法的matlab程序(初步)”这篇文章中已经用matlab程序对iris数据库进行简单的实现,下面的程序最终的目的是求准确度. 作者:凯鲁嘎吉 - 博客园 h ...