从 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, 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 中,从而在子类中能调用这些属性和方法。

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

项目地址

原文地址:https://www.cnblogs.com/MuYunyun/p/9298089.html

时间: 2024-07-29 14:34:56

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

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

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (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 系列 -- 优化 set

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

本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/...) 项目地址 从 0 到 1 实现 React 系列 -- JSX 和 Virtual DOM 从 0 到 1 实现 React 系列 -- 组件和 state|props 从 0 到 1 实现 React 系列 -- 生命周期和 diff 算法 从 0 到 1 实现 React 系列 -- 优化 se

为什么react的组件要super(props)

为什么react的组件要super(props) 摘自 https://segmentfault.com/q/1010000008340434 (非原创) 如图,我知道supert是继承constructor的参数,但是为什么在react里面,有一些组件使用了super(props),而有一些没有写,还有在es6里就只是写了supert(),这些区别在哪呢?以及这里的这个constructor(props)...super(props)是起到什么作用呢 这个是全代码: 已采纳 调用super的原

React系列--组件Component

1. 组件其实可以理解为 虚拟 dom对象的集合.也是一个虚拟dom. 1.1组件定义有两种方式 // 1) 工厂函数,无状态,(定义一些比较简洁的组件,推荐使用) function MyComponent1(){ return <h2>工厂函数</h2>; } // 2) ES6 class方式定义组件      //推荐 class MyComponent2 extends React.Component{ render(){ return <h3>ES6 clas

React 的组件与 this.props对象

1.组件 React 允许将代码封装成组件,然后像插入普通 HTML 标签一样,在网页中插入这个组件.React.createClass 的方法就是用于生成一个组件类. 2.this.props对象 即是对组件标签中的标签属性和子节点构成的集合. 控制台显示为 注:this.props.children 的值有3种可能:即当前组件没有字节点时,它就是undefined:如果有一个子节点,数据类型是object:如果有多个子节点时,数据类型就是array.所以处理this.props.childr

【React Native开发】React Native控件之Touchable*系列组件详解(18)

转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50630984 本文出自:[江清清的博客] (一)前言 [好消息]个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org 今天我们一起来看一下Touchable*系列组件的使用详解,该系列组件包括四种分别为:TouchableHighlight,TouchableNativeFeedback,Touch

React系列文章:无状态组件生成真实DOM结点

在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name}) { return <div>{`hello ${name}`}</div>; }; const App = <Greeting name="scott"/>; console.log(App); ReactDOM.render(App, docum

React 系列教程 1:实现 Animate.css 官网效果

前言 这是 React 系列教程的第一篇,我们将用 React 实现 Animate.css 官网的效果.对于 Animate.css 官网效果是一个非常简单的例子,原代码使用 jQuery 编写,就是添加类与删除类的操作.这对于学习 React 来说是一个非常简易的例子,但是我并不会在教程中介绍相关的前置知识,比如 JSX.ES6 等,对于小白来说可能还会有一些困惑的地方,所以还要了解一下 React 相关的基础知识.虽然 React 有很多值得深究的知识,但这个系列教程并不会涉及高大深的内容

【05】react 之 组件state

1.1.  状态理解 React的数据流:由父节点传递到子节点(由外到内传递),如果顶层组件某个prop改变了,React会向下传递,重新渲染所有使用过该属性的组件.除此之外React 组件内部还具有自己的状态,这些状态只能在组件内部修改.通过与用户的交互(点击),实现不同状态(显示.隐藏.数量增加...),然后渲染UI,让用户界面和数据保持一致.React中只需更新组件的state,然后根据新的 state 重新渲染用户界面. this.props  属性:由父节点传入到组件内部,只读,不可修