从 0 到 1 实现 React 系列 —— 2.组件和 state|props
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (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, 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的更多相关文章
- 从 0 到 1 实现 React 系列 —— 1.JSX 和 Virtual DOM
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 从 0 到 1 实现 React 系列 —— 5.PureComponent 实现 && HOC 探幽
本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/...) ...
- 从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 从 0 到 1 实现 React 系列 —— 3.生命周期和 diff 算法
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 学习React系列(十)——Render Props
解决问题:将行为封装,供多个组件使用(在多个组件之间分享某段代码) 组件中的props属性中包含一个"render"属性(该属性为一个返回值为元素的方法),然后在该组件的rende ...
- React高阶组件 和 Render Props
高阶组件 本质 本质是函数,将组件作为接收参数,返回一个新的组件.HOC本身不是React API,是一种基于React组合的特而形成的设计模式. 解决的问题(作用) 一句话概括:功能的复用,减少代码 ...
- 说说React组件的State
说说React组件的State React的核心思想是组件化的思想,应用由组件搭建而成, 而组件中最重要的概念是State(状态). 正确定义State React把组件看成一个状态机.通过与用户的交 ...
- React组件的State
React组件的State 1.正确定义State React把组件看成一个状态机.通过与用户的交互,实现不同状态,然后渲染UI,让用户界面和数据保持一致.组件的任何UI改变,都可以从State的变化 ...
- React中父子组件数据传递
Vue.js中父子组件数据传递:Props Down , Events Up Angular中父子组件数据传递:Props Down, Events Up React中父子组件数据传递:Prop ...
随机推荐
- Python textwrap模块(文本包装和填充)
textwrap提供函数wrap().fill().indent().dedent()和以及TextWrapper类. 通常包装或者填充一两个字符串使用wrap()和fill().其他情况使用Text ...
- 完全数java
完全数:小于本身的所有因子的和(包括1) public class test01 { public static void main(String[] args) { Scanner scanner= ...
- javascript 重要属性之prototype(继承)
转载猫猫小屋 http://www.maomao365.com/?p=831 在javascript中每一个函数都拥有 prototype属性,在javascript中使用prototype,可以向已 ...
- 利用dockerfile制作基于centos7的lnmp镜像(亲测,详细版)
首先呢,这篇文章,也是小弟参考了许多文章,自己整理出来的,有很多不足之处还有待加强,期待各位评论. > LNMP 是代表 Linux 系统下的 Nginx.Mariadb.PHP 相结合而构建成 ...
- 使用GDB调试gp(转载)
使用 gdb 调试 postgres greenplum zalax3030人评论715人阅读2012-07-11 10:07:15 错误信息的获取途径有几种 : 1. 最简单的就是看Postg ...
- Ubuntu 12.04上安装Hadoop并运行
Ubuntu 12.04上安装Hadoop并运行 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 在官网上下载好四个文件 在Ubuntu的/home/w ...
- Java虚拟机(Java Virtual Machine)
JVM(Java Virtual Machine),Java虚机机,是JDK最底层的东西.只要能将源代码编译成字节码(.class)文件,就可以由JVM在不同平台上解释成机器指令来执行.所以,Java ...
- 【大数据技术】Flink
“下一代大数据处理引擎王者” Apache Flink 它既能保证数据一致性“Exactly Once",又能实时快速的处理海量数据.与生俱来的 Watermark 功能让它能对复杂数据乱序 ...
- A. On The Way to Lucky Plaza 概率 乘法逆元
A. On The Way to Lucky Plaza time limit per test 1.0 s memory limit per test 256 MB input standard i ...
- P2066 机器分配 DP
题目描述 总公司拥有高效设备M台,准备分给下属的N个分公司.各分公司若获得这些设备,可以为国家提供一定的盈利.问:如何分配这M台设备才能使国家得到的盈利最大?求出最大盈利值.其中M≤15,N≤10.分 ...