从 0 到 1 实现 React 系列 —— 组件和 state|props
阅读源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/...)
组件即函数
在上一篇 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 中,从而在子类中能调用这些属性和方法。
在下篇,我们会继续实现生命周期机制,如有疏漏,欢迎斧正。
项目地址
从 0 到 1 实现 React 系列 —— 组件和 state|props相关推荐
- 从 0 到 1 实现 React 系列 —— 2.组件和 state|props
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 从 0 到 1 实现 React 系列 —— 1.JSX 和 Virtual DOM
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- 从 0 到 1 实现 React 系列 —— 4.优化setState和ref的实现
看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...
- [react] 在React中组件的state和setState有什么区别?
[react] 在React中组件的state和setState有什么区别? state:初始化状态 setState:对状态进行更新 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, ...
- React基础——组件状态state、属性props
import React from 'react'; // 此句不能少 import ReactDom from 'react-dom';class Taggle extends React.Comp ...
- React 的组件与 this.props对象
1.组件 React 允许将代码封装成组件,然后像插入普通 HTML 标签一样,在网页中插入这个组件.React.createClass 的方法就是用于生成一个组件类. 2.this.props对象 ...
- react系列知识---11组件间抽象
mixin mixin :将一个模块混入到一个另一个模块中,或是一个 类中 封装 mixin 方法 const mixin = function (obj, mixins) {const newObj ...
- React学习(六)-React中组件的数据-state
虽互不曾谋面,但希望能和你成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 撰文 | 川川 ID:suibichuanji 点击下方 ...
最新文章
- Xshell链接远程服务器调用Pycharm的方法
- 照片处理和分享的思路
- httpd开启status模块_开启Apache Server Status
- X-lab 开放实验室开源创新的故事
- 安装openstack_午餐前如何安装OpenStack Cloud
- php dom对象,JavaScript_JavaScript DOM 对象深入了解,什么叫DOM,DOM是文档对象模型( - phpStudy...
- S5P4418iNand清空方法
- 使用java jpcap实现网络嗅探器
- Newtonsoft 转换json
- php++jsapi,jsapi:云之家js-api文档 - 云之家·开放平台
- 服务器server2012重置开机密码
- arcgis小班编号问题 工具箱来喽
- 因果性与因果模型 | 中国人民大学哲学与认知科学明德讲坛
- 汽车美容店管理系统如何管理店铺数据?
- volatile禁止重排序详解
- win2003serve IIS6.0搭载多个站点
- 要走得快,就一个人走;要走得远,就一起走
- 做管件网络销售之前,必须要做的三件事
- php++内购续期订阅,关于自动订阅(auto renewal)
- 程序员英文简历范例(项目经理)
热门文章
- Linux 离线安装软件
- wxpython SizerItem的大小控制
- 18. 爱吃皮蛋的小明(斐波那契数列)
- C#面向对象2 静态类、静态成员的理解
- [转贴]Silverlight Socket 实现收发信息
- Windows xp 定时关机命令
- VS2010编译器经常遇到的小问题
- AndroidStudio_安卓原生开发_自定义蒙板弹出框WaitDialog---Android原生开发工作笔记134
- 大数据_MapperReduce_Hbase的优化_存数据_自动计算分区号 自动计算分区键---Hbase工作笔记0027
- ARM裸机工作笔记0001---ARM那些事