react 的第一个组件

写了 react 有一个半月,现在又有半个月没写了,感觉对其仍旧比较陌生。

本文分两部分,首先聊一下 react 的相关概念,然后不使用任何语法糖(包括 jsx)或可能隐藏底层技术的便利措施来构建 React 组件

Tip:从一项新技术的底层元素起步有利于使用者更好的长期使用它

跨平台

大部分 react 应用是在 Web 平台上。而 React NativeReact VR 这样的项目则创造了 react 应用在其他平台上运行的可能

React 应用主要成分

组件

组件是 React 中最基本单元

组件通常对应用户界面的一部分,比如导航。也可以担任数据格式化等职责。

可以将任何东西作为组件,尽管并不是所有东西作为组件都有意义。

如果将整个界面作为组件,并且没有子组件或进一步的细分,那么对自己并没有什么帮助。倘若,将界面不同部分拆解成可以组合,复用的部分,却很有帮助。

组件具有良好的封装性复用性组合性。有助于为使用者提供一个更简单的方式来思考和构建用户界面。使用 React 构建应用就像使用积木来搭建项目,而构建应用时有取之不尽的“积木”

将 UI 分解成组件可以让人更轻松的处理应用不同的部分。

组件需要一起工作,也就是说组件可以组合起来形成新的组件。组件组合也是 React 最强大的部分之一。

如果身处一个中大型团队,可以将组件发布到私有注册中心(npm 或者其他)

React 组件还有一个方面就是生命周期方法。当组件经过其生命周期的不同时期时(挂在、更新、卸载等),可以使用可预测、定义良好的方法。

React 库

React 核心库与 react-dom 和 react-native 紧密配合,侧重组件的规范和定义。能让开发者构建一个组件树,该组件树能够被浏览器和其他平台所使用。

react-dom 就是一个渲染器。针对浏览器环境和服务端渲染。

比如我们要将组件渲染到浏览器,就得用到 react-dom。

React Native 库专注于原生平台,能够为 ios、android 和其他平台创建 react 应用。

第三方库

React 不自带 http 等其他前端常用工具库。开发者可以自由的选择对于工作最好的工具。

react 的权衡

react 属于 专一型,主要关注 UI 试图方面。

而 angular 属于 通用型,其内置了许多解决方案,例如 http 调用、路由、国际化、字符串和数字格式化...

Tip:通常一些优秀的团队会用这两种方式。

React 的创建主要用于 Facebook 的 UI 需求。虽然大多数的 web 应用在此范围之内,但也有一些应用不在。

React 是一种抽象,也存在抽象的代价。React 以特定的方式构建并通过 api 向外暴露,开发者会失去对底层的可见性。当然 React 也提供了紧急出口,让开发者深入较低的抽象层级,仍然可以使用 jQuery,不过需要以一种兼容 React 的方式使用。

有时还需要为 React 的行事方式买单。或许会影响应用的小部分(即不太适合用 React 的方式来工作)

使用 React 时所做的权衡有助于使用者成为更好的开发者。

虚拟 Dom

React 旨在将复杂的任务简单化,把不必要的复杂性从开发者身上剥离出来。

鼓励开发者使用声明式的编程而非命令式,也就是开发者声明组件在不同状态下的行为和外观即可,React 负责渲染以及更新 UI,并将性能做到恰到好处。从而让研发人员腾出时间思考其他方面。

驱动这些的主要技术之一就是虚拟dom

Tip:有关虚拟dom 的介绍可以参考 vue 快速入门-虚拟dom

虚拟 Dom 不是我们关注的重点。这正是 React 简单 的地方:开发者被解放出来,去关注最关注的部分。

React 的简单、非固化

什么使 React 成为大型团队的宠儿?首先是简单,其次是非固化

简单的技术让人更容易理解和使用。

React 是一个非常轻量的库,只关注应用的视图。更加容易与使用者当前的技术集成,并在其他方面为使用者留下了选择的空间。一些功能固化的框架和库要求使用者要么全盘接受要么彻底不用。

简单和非固化的特性,以及恰到好处的性能,让它非常适合大大小小的项目。

组件间的关系

组件可以独立存在,也可用来创建其他组件。人们认为组件可以创建很多不同类的关系,从某种意义这是对的。

但组件更多的是以灵活的方式被使用,应该关注其独立性和常常不带任何负担,可组合使用。所以组件只关注其父母和孩子,兄弟关系可以不管。

建立组件关系的过程对每个团队或项目都不尽相同,组件关系也可能会随时间而改变,我们可以不期望一次就建立完美,也无需太过担心,因为 React 会让我们的 UI 迭代没那么困难。

搭建组件的框架

首先我们将组件的框架写好:

  1. <body>
  2. <div id="root">
  3. <!-- 此元素的内容将替换为您的组件 -->
  4. </div>
  5. <!-- react 库 -->
  6. <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  7. <!-- 用于处理 Dom 的 react 包 -->
  8. <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  9. <!-- 使用 PropTypes 进行类型检查 -->
  10. <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
  11. <script>
  12. const reactElem = React.createElement(
  13. 'h1',
  14. {title: 'i am h1'},
  15. 'Hello, world!'
  16. )
  17. ReactDOM.render(
  18. reactElem,
  19. document.getElementById('root')
  20. );
  21. </script>
  22. </body>

Tip:这是一个普通的 html 页面,直接通过 vscode 的 Live Server 插件运行即可

运行后的网页显示 Hello, world!。生成的元素结构如下:

  1. <div id="root">
  2. <h1 title="i am h1">Hello, world!</h1>
  3. </div>

下面我们稍微分析一下这个页面:

首先定义了一个 div 元素,接着引入三个包,作用如下:

  • react.js,React 的核心库,用于定义组件的规范
  • react-dom.js,渲染器,用于浏览器和服务端渲染,用于创建组件和管理组件
  • prop-types.js,传递给组件的数据做类型检查

接着通过 React.createElement 创建一个 react 元素。

  1. React.createElement(
  2. type,
  3. [props],
  4. [...children]
  5. )

Tip:react 元素是什么?

  • react 元素是创建起来开销极小的普通对象
  • react 元素是构成 React 应用的最小砖块。react 元素之于React如同DOM元素之于DOM,react 元素组成了虚拟 DOM
  • react 组件是由 react 元素构成的

最后使用 ReactDOM.render 将 React 元素渲染到 div#root 中。

  1. // 在提供的 container 里渲染一个 React 元素,并返回对该组件的引用
  2. ReactDOM.render(element, container[, callback])

Tip:调用 react-dom 的 render() 方法来让 React 将组建渲染出来,并对组件进行管理。

React 元素

React 元素是你想让 React 渲染的东西的轻量表示。它可以表示为一个 Dom 元素,上文我们已经用其创建了一个 h1 的 dom 元素。

有必要再来分析一下 createElement() 的参数:

  1. // 创建并返回指定类型的新 React 元素。其中的类型参数既可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
  2. React.createElement(
  3. type,
  4. [props],
  5. [...children]
  6. )
  1. const reactElem = React.createElement(
  2. 'h1',
  3. {title: 'i am h1'},
  4. 'Hello, world!'
  5. )
  • type,一个 html 标签("div"、"h1")或 React 类
  • props,指定 html 元素上要定义哪些属性或组件类的实例上可以使用哪些属性
  • children,还记得 React 组件是可以组合的吗?

一句话:React.createElement() 在问:

  • 我在创建什么? ,是 Dom 元素,是 React 组件,还是React fragment。
  • 我怎么配置它?
  • 它包含什么?

假如我们需要在页面显示如下元素:

  1. <div>
  2. <h2>i am h2</h2>
  3. <a href="www.baidu.com">go baidu</a>
  4. <p>
  5. <em>i am em element</em>
  6. </p>
  7. </div>

可以这么写:

  1. const c = React.createElement
  2. const reactElem2 = React.createElement(
  3. 'div',
  4. {},
  5. c('h2', {}, 'i am h2'),
  6. c('a', {href: 'www.baidu.com'}, 'go baidu'),
  7. c('p', {},
  8. c('em', {}, 'i am em element')
  9. )
  10. )

虚拟 DOM 树

React 是怎么把那么多 React.createElement 转换成屏幕上看到的东西的?这里得用到虚拟 dom。

虚拟 dom 和真实 dom 有着相似的结构。

为了从 React 元素中形成自己的虚拟 DOM 树,React 会对 React.createElement 的全部 children 属性进行求值,并将结果传递给父元素。就像一个小孩反复再问 X是什么?,直到理解 X 的每个细节,直到他能形成一棵完整的树。

React 组件

看看这段代码,我们创建了一个 React 元素并将其放入 dom 中:

  1. <script>
  2. const c = React.createElement
  3. const reactElem = React.createElement(
  4. 'div',
  5. {},
  6. c('h2', {}, 'i am h2'),
  7. c('a', {href: 'www.baidu.com'}, 'go baidu'),
  8. c('p', {},
  9. c('em', {}, 'a am em element')
  10. )
  11. )
  12. ReactDOM.render(
  13. reactElem,
  14. document.getElementById('root')
  15. );
  16. </script>

如果我们需要扩展 reactElem 的功能、样式以及其他UI相关?这时可以使用组件

组件可以将这些有效的组织在一起。

所以,要真正构建东西,不仅仅需要 React 元素,还需要组件。

React 组件就像是 React 元素,但 React 组件拥有更多特性。React 组件是帮助将 React 元素和函数组织到一起的类

我们可以使用函数或 js 类创建组件。

使用 es6 的 class 来定义组件。就像这样:

  1. class MyComponent extends React.Component {
  2. // 必须定义 render()。否则会报错:
  3. // MyComponent(...): No `render` method found on the returned component instance: you may have forgotten to define `render`.
  4. render() {
  5. // 返回单个 React 元素或 React 元素的数组
  6. return reactElem
  7. }
  8. }

通常需要至少定义一个 render() 方法,几乎任何向屏幕显示内容的组件都带有 render 方法

Tip:那些不直接显示任何东西而是修改或增强其他组件的组件(称高阶组件),后续再讨论。

我们将上面示例改成组件形式:

  1. <script>
  2. class MyComponent extends React.Component{
  3. render(){
  4. const c = React.createElement
  5. return React.createElement(
  6. 'div',
  7. {},
  8. c('h2', {}, this.props.cnt1),
  9. c('a', {href: 'www.baidu.com'}, 'go baidu'),
  10. // class 属性在需要改为 className
  11. c('p', {className: this.props.aClass},
  12. c('em', {}, this.props.cnt2)
  13. )
  14. )
  15. }
  16. }
  17. </script>
  18. <script>
  19. // createElement 第一个参数可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型
  20. const App = React.createElement(MyComponent, {
  21. cnt1: 'i am h2',
  22. aClass: 'p-class',
  23. cnt2: 'a am em element'
  24. })
  25. ReactDOM.render(
  26. App,
  27. document.getElementById('root')
  28. );
  29. </script>

生成的 html 如下:

  1. <div>
  2. <h2>i am h2</h2>
  3. <a href="www.baidu.com">go baidu</a>
  4. <p class="p-class">
  5. <em>a am em element</em>
  6. </p>
  7. </div>

React 中通过 this.props 就能获取传递给组件的属性。

this.props 是怎么来的

MyComponent 中没有初始化 props 的代码,既然自己没做,那么肯定是父类帮忙做了。

就像这样:

  1. <script>
  2. // 父类
  3. class Rectangle {
  4. constructor() {
  5. // 子类接收的参数,这里 arguments 都能接收到
  6. const args = Array.from(arguments)
  7. // args= (3) [{…}, 'b', 'c']
  8. console.log('args=', args)
  9. this.props = args[0]
  10. }
  11. }
  12. // 子类
  13. class Square extends Rectangle {
  14. render(){
  15. // name= pjl
  16. console.log('name=', this.props.name)
  17. }
  18. }
  19. let square = new Square({name: 'pjl'}, 'b', 'c')
  20. square.render()
  21. </script>

如果需要自己写 constructor ,则需要手动调用 super(),否则会报错。就像这样:

  1. // 控制台输入如下代码,报错
  2. // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  3. // Uncaught ReferenceError:在访问“this”或从派生构造函数返回之前,必须在派生类中调用超级构造函数
  4. class A{}
  5. class B extends A{
  6. constructor(){
  7. }
  8. }
  9. let b = new B()

Tip: 有关 super 更多介绍请看 这里

constructor(props) 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。通常,构造函数仅用于以下两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例

类型检测

类组件能使用自定义属性。

通过组件好像能创建自定义 html 元素,而且还能做得更多。

能力越大,责任也越大,我们需要使用一些方法来验证所使用的属性,防止缺陷、规划组件所使用的数据种类。

在上面示例基础上,我们增加类型检测:

  1. <script>
  2. class MyComponent extends React.Component {
  3. ...
  4. }
  5. // 属性类型
  6. MyComponent.propTypes = {
  7. cnt1: PropTypes.number,
  8. // 注:函数不是 function,而是 func
  9. // 函数类型,并且必填
  10. func: PropTypes.func.isRequired
  11. }
  12. // 默认值
  13. MyComponent.defaultProps = {
  14. cnt1: 'defaultName'
  15. }
  16. </script>

类型检测生效了。控制台报错如下:

  1. Warning: Failed prop type: Invalid prop `cnt1` of type `string` supplied to `MyComponent`, expected `number`
  2. 警告:失败的属性类型:提供给“MyComponent”的类型为“string”的属性“cnt1”无效,应为“number`
  3. Warning: Failed prop type: The prop `func` is marked as required in `MyComponent`, but its value is `undefined`.
  4. 警告:失败的属性类型:属性“func”在“MyComponent”中标记为必需,但其值为“undefined”。

除了控制台发出 Warning,页面显示仍旧正常。

这里其实就是按照特定规定,给 MyComponent 类增加了两个静态成员,用于类型检测。我们可以自己模拟一下,请看示例:

  1. <script>
  2. class Rectangle {
  3. constructor() {
  4. this.props = arguments[0]
  5. // 模拟类型验证
  6. this.validate(this.constructor)
  7. }
  8. validate(subClass) {
  9. Object.keys(subClass.propTypes).forEach(key => {
  10. const propType = subClass.propTypes[key]
  11. const type = typeof this.props[key]
  12. if (type !== propType) {
  13. console.error(`Warning ${key} 属性 - 期待类型是 ${propType},所传入的类型确是 ${type}`)
  14. }
  15. })
  16. }
  17. }
  18. class Square extends Rectangle {
  19. render() {
  20. }
  21. }
  22. Square.propTypes = {
  23. name: 'string',
  24. age: 'number'
  25. }
  26. let square = new Square({ name: 18, age: 'pjl' })
  27. square.render()
  28. </script>

类型检测结果如下:

  1. // 浏览器控制台输出:
  2. Warning name 属性 - 期待类型是 string,所传入的类型确是 number
  3. Warning age 属性 - 期待类型是 number,所传入的类型确是 string

现在这么写有些零散,我们可以使用 static 语法来对其优化。就像这样:

  1. class Square extends Rectangle {
  2. static propTypes = {
  3. name: 'string',
  4. age: 'number'
  5. }
  6. render(){}
  7. }

Tip: 有关 static 更多介绍可以百度 mdn static

嵌套组件

我们已经创建了一个类组件,并传入了一些属性,现在我们可以尝试嵌套组件。

前面我们已经提到,组件组合是 React 中非常强大的功能。比如一个页面,我们可以通过组件进行拆分,单独开发,最终却是需要将组件组合成一个页面,否则就不好玩了。

将上面组件拆成两个,稍作变动,代码如下:

  1. <script>
  2. class MyComponent extends React.Component {
  3. render() {
  4. const c = React.createElement
  5. return React.createElement(
  6. 'div',
  7. { className: 'parent-class' },
  8. c('h2', {}, this.props.cnt1),
  9. // 核心是:this.props.children。
  10. this.props.children
  11. )
  12. }
  13. }
  14. class MySubComponent extends React.Component {
  15. render() {
  16. const c = React.createElement
  17. return React.createElement(
  18. 'div',
  19. { className: 'sub-class' },
  20. c('a', { href: 'www.baidu.com' }, 'go baidu'),
  21. c('p', { className: this.props.aClass },
  22. c('em', {}, this.props.cnt2)
  23. )
  24. )
  25. }
  26. }
  27. </script>
  28. <script>
  29. // createElement 第一个参数可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型
  30. const App = React.createElement(MyComponent, {
  31. cnt1: 'i am h2',
  32. },
  33. React.createElement('p', {}, 'i am p element'),
  34. React.createElement(MySubComponent, {
  35. aClass: 'p-class',
  36. cnt2: 'a am em element'
  37. }
  38. ))
  39. ReactDOM.render(
  40. App,
  41. document.getElementById('root')
  42. );
  43. </script>

核心是 this.props.children,每个组件都可以获取到 props.children。最终渲染 html 结构如下:

  1. <div id="root">
  2. <div class="parent-class">
  3. <h2>i am h2</h2>
  4. <!-- this.props.children -->
  5. <p>i am p element</p>
  6. <div class="sub-class">
  7. <a href="www.baidu.com">go baidu</a>
  8. <p class="p-class">
  9. <em>a am em element</em>
  10. </p>
  11. </div>
  12. <!-- /this.props.children -->
  13. </div>
  14. </div>

动态组件

现在我们已经为组件添加了 render 方法和一些 propTypes。上面示例也仅仅显示一些静态文案,但要创建动态组件,远不止这些。

React 提供了某些特殊方法,当 React 管理虚拟 dom 时,react 会按顺序调用它们,render 方法只是其中之一。

状态

状态可以让组件交互并鲜活起来。

Tip: 状态其他特性如下:

  • 状态可以理解成事物在某一时刻的信息。可以分成可变状态和不可变状态。简单区分就是事物创建之后是否能变化?如果可以,则是可变状态
  • 通过 this.state 访问的是可变状态;通过 this.props 访问的是不可变状态
  • state 和 props 是数据的传输工具,这些数据构成应用并使其有用。
  • State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件 —— 官网
  • react 中的 props 用来接收父组件传来的属性,state 是私有属性
  • 应该在什么时候使用 state?想要改变存储在组件中的数据时。

下面我们使用一下 state,既然 state 是可变状态,那么我们就创建一个表单组件,里面有一个 input,一个提交按钮。

代码如下:

  1. <script>
  2. class MyComponent extends React.Component {
  3. render() {
  4. const c = React.createElement
  5. return React.createElement(
  6. 'div',
  7. { className: 'parent-class' },
  8. this.props.children
  9. )
  10. }
  11. }
  12. class MySubComponent extends React.Component {
  13. constructor(props){
  14. // 如果不需要使用 this.props,则可以是 super()
  15. super(props)
  16. // 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this
  17. this.state = {age: 18}
  18. }
  19. render() {
  20. const c = React.createElement
  21. return React.createElement(
  22. 'form',
  23. { className: 'sub-class' },
  24. c('p', { },
  25. c('input', {type: 'text', value: this.state.age})
  26. ),
  27. c('p', { },
  28. c('input', {type: 'submit', value: 'submit'})
  29. ),
  30. )
  31. }
  32. }
  33. </script>

生成的表单也很简单,状态数据 age 也已经在 input 元素中成功显示:

  1. <div id="root">
  2. <div class="parent-class">
  3. <form class="sub-class">
  4. <p><input type="text" value="18"></p>
  5. <p><input type="submit" value="submit"></p>
  6. </form>
  7. </div>
  8. </div>

现在需要专门的方法更新 state 中的数据。不能直接修改(例如 this.state.age = 19),因为 React 需要跟踪状态,并保证虚拟 dom 和真实 dom 的同步。得通过 React 提供的特殊通道(this.setState()) 来更新 React 类组件中的状态。

setState 不会立即更新组件,React 会根据状态变化批量更新以便使效率最大化,也就是说 React 会以它最高效的方法基于新状态更新 dom,做到尽可能快。

Tip: 不要直接修改 state 的示例请看 这里

事件与 React 如何协作

以前我们直接操作 dom,于是可以通过 addEventListener 注册事件;现在不直接操作 dom,而是和 React 元素打交道,那么 React 应该提供对应的事件机制,最好和我们之前的习惯相同,而 React 确实是这样做的。

React 实现了一个合成事件系统作为虚拟 Dom 的一部分,它会将浏览器中的事件转为 React 应用的事件。可以设置响应浏览器事件的事件处理器,就像通常用 js 那样做就好。区别是 React 的事件是设置在 React 元素或组件自身上,而不是用 addEventListener。

React 能监听浏览器中很多不同事件,涵盖了几乎所有的交互(点击、提交、滚动等)

接下来我们就可以用来自这些事件(比如文本变化时的事件 onchange)的数据来更新组件状态。

接着上面示例,需求是:更改 input[type=text] 的值,对应 state 中的 age 也会同步,点击 submit 能提交。

代码如下:

  1. <script>
  2. class MyComponent extends React.Component {
  3. render() {
  4. const c = React.createElement
  5. return React.createElement(
  6. 'div',
  7. { className: 'parent-class' },
  8. this.props.children
  9. )
  10. }
  11. }
  12. class MySubComponent extends React.Component {
  13. constructor(props) {
  14. // 如果不需要使用 this.props,则可以是 super()
  15. super(props)
  16. // 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this
  17. this.state = { age: 18 }
  18. // 自定义的方法,react 没有帮我们处理 this。所以这里需要我们自己绑定一下
  19. this.handleTextChange = this.handleTextChange.bind(this)
  20. this.handleSubmit = this.handleSubmit.bind(this)
  21. }
  22. handleTextChange(evt) {
  23. // 不手动绑定 this,则为 undefined
  24. // console.log('this', this)
  25. const v = evt.target.value
  26. // 用来自事件的数据更新组件状态。否则界面是看不到 age 的最新值的
  27. this.setState({ age: v })
  28. }
  29. // React 要阻止默认行为,必须显式的使用 preventDefault
  30. handleSubmit(evt) {
  31. evt.preventDefault()
  32. // 提交表单。state {age: 18}
  33. console.log('提交表单。state', this.state)
  34. }
  35. render() {
  36. const c = React.createElement
  37. return React.createElement(
  38. 'form',
  39. {
  40. className: 'sub-class',
  41. onSubmit: this.handleSubmit
  42. },
  43. c('p', {},
  44. c('input', {
  45. type: 'text',
  46. value: this.state.age,
  47. // React 事件的命名采用小驼峰式(camelCase),而不是纯小写。例如在 html 中通常都是小写(onclick)
  48. onInput: this.handleTextChange
  49. })
  50. ),
  51. c('p', {},
  52. c('input', { type: 'submit', value: 'submit' })
  53. ),
  54. )
  55. }
  56. }
  57. </script>

函数传递数据

利用函数可以将子组件的数据传递给父组件。核心代码如下:

  1. class MyComponent extends React.Component {
  2. constructor(props) {
  3. super(props)
  4. this.handleSubmit = this.handleSubmit.bind(this)
  5. }
  6. handleSubmit(data) {
  7. console.log('提交表单 data=', data)
  8. }
  9. render() {
  10. const c = React.createElement
  11. return React.createElement(
  12. 'div',
  13. { className: 'parent-class' },
  14. React.createElement(MySubComponent, {
  15. aClass: 'p-class',
  16. cnt2: 'a am em element',
  17. onFormSubmit: this.handleSubmit
  18. })
  19. )
  20. }
  21. }
  22. class MySubComponent extends React.Component {
  23. handleSubmit(evt) {
  24. evt.preventDefault()
  25. this.props.onFormSubmit(this.state)
  26. }
  27. }

数据流向

在 React 中,数据自顶向下流动,可以通过 props 向子组件传递信息并在子组件中使用这些信息。表明可以将子组件的数据存储在父组件中,并从那里将数据传递给子组件。做个实例来验证一下,定义三个组件(A、B、C),结构如下:

  1. <div class='componentA'>
  2. <p class='componentB'> apple </p>
  3. <button class='componentC'>add apple</button>
  4. </div>

数据存在 AComponent 中,每点击一次 CComponent 组件,就会要求 AComponent 增加一个 apple,渲染到页面的 BComponent 组件也相应增加。

全部代码如下:

  1. <script>
  2. class AComponent extends React.Component {
  3. constructor(props) {
  4. super(props)
  5. this.state = {apples: this.props.apples}
  6. this.handleAddApple = this.handleAddApple.bind(this)
  7. }
  8. handleAddApple(data) {
  9. this.setState({apples: [data, ...this.state.apples]})
  10. }
  11. render() {
  12. const c = React.createElement
  13. return c(
  14. 'div',
  15. { className: 'componentA' },
  16. this.state.apples.map((item, index) => c(BComponent, {
  17. content: item,
  18. // 需要增加 key 属性,否则报错:
  19. // Warning: Each child in a list should have a unique "key" prop.
  20. key: index
  21. })),
  22. c(CComponent, {
  23. onHandleAddApple: this.handleAddApple
  24. })
  25. )
  26. }
  27. }
  28. class BComponent extends React.Component {
  29. render() {
  30. const c = React.createElement
  31. return React.createElement(
  32. 'p',
  33. {
  34. className: 'componentB',
  35. // key: this.props.key
  36. },
  37. this.props.content
  38. )
  39. }
  40. }
  41. class CComponent extends React.Component {
  42. constructor(props) {
  43. super(props)
  44. this.handleAdd = this.handleAdd.bind(this)
  45. }
  46. // React 要阻止默认行为,必须显式的使用 preventDefault
  47. handleAdd(evt) {
  48. this.props.onHandleAddApple('apple')
  49. }
  50. render() {
  51. const c = React.createElement
  52. return React.createElement(
  53. 'button',
  54. {
  55. className: 'componentC',
  56. onClick: this.handleAdd
  57. },
  58. 'add apple'
  59. )
  60. }
  61. }
  62. </script>
  63. <script>
  64. const App = React.createElement(AComponent, {
  65. apples: ['apple']
  66. },
  67. )
  68. ReactDOM.render(
  69. App,
  70. document.getElementById('root')
  71. );
  72. </script>

jsx

JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖 —— react 官网-深入 JSX

我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式 —— react 官网-JSX 简介

jsx 让人编写类似于(但不是) HTML 的代码。

将上面增加 apple 的例子改为 jsx。全部代码如下:

  1. <body>
  2. <div id="root"></div>
  3. <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  4. <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  5. <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
  6. <!-- Babel 能够转换 JSX 语法 -->
  7. <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  8. <script type="text/babel">
  9. class AComponent extends React.Component {
  10. constructor(props) {
  11. super(props)
  12. this.state = { apples: this.props.apples }
  13. this.handleAddApple = this.handleAddApple.bind(this)
  14. }
  15. handleAddApple(data) {
  16. this.setState({ apples: [data, ...this.state.apples] })
  17. }
  18. render() {
  19. return <div className='componentA'>
  20. {
  21. this.state.apples.map(
  22. (item, index) =>
  23. (<BComponent content={item} key={index} />)
  24. )
  25. }
  26. <CComponent onHandleAddApple={this.handleAddApple} />
  27. </div>
  28. }
  29. }
  30. class BComponent extends React.Component {
  31. render() {
  32. return <p className='componentB'>{this.props.content}</p>
  33. }
  34. }
  35. class CComponent extends React.Component {
  36. constructor(props) {
  37. super(props)
  38. this.handleAdd = this.handleAdd.bind(this)
  39. }
  40. handleAdd(evt) {
  41. this.props.onHandleAddApple('apple')
  42. }
  43. render() {
  44. return <button className="componentC" onClick={this.handleAdd}>add apple</button>
  45. }
  46. }
  47. </script>
  48. <script type="text/babel">
  49. const App = <AComponent apples={['apple']} />
  50. ReactDOM.render(
  51. App,
  52. document.getElementById('root')
  53. );
  54. </script>
  55. </body>

jsx 除了类似于 HTML 且语法简单,另一个好处是声明式封装。通过将组成视图的代码和相关联的方法包含在一起,使用者创建了一个功能组。本质上,需要知道的有关组件的所有信息都汇聚在此,无关紧要的东西都被隐藏起来,意味着使用者更容易的思考组件,并且更加清楚他们作为一个系统是如何工作的。

主要注意,JSX 不是 html,只会转译成常规 React 代码。它的语法和惯例也不完全相同,需要关注一些细微的差异(偶尔有些“破费思量之处”),比如:

  • 自定义组件使用大写字母开头。用于区分自定义组件和原生 html
  • 属性表达式写在大括号内。例如 <AComponent apples={['apple']} />
  • 省略一个属性的值,jsx 会视为 true。要传 false,必须使用属性表达式
  • 要在元素内部插入表达式的值,也必须使用大括号

Tip:比如class 得换成 className,更多 jsx 语法规则请看 这里

react实战系列 —— react 的第一个组件的更多相关文章

  1. react实战系列 —— React 中的表单和路由的原理

    其他章节请看: react实战 系列 React 中的表单和路由的原理 React 中的表单是否简单好用,受控组件和非受控是指什么? React 中的路由原理是什么,如何更好的理解 React 应用的 ...

  2. react实战 系列 —— React 的数据流和生命周期

    其他章节请看: react实战 系列 数据流和生命周期 如何处理 React 中的数据,组件之间如何通信,数据在 React 中如何流动? 常用的 React 生命周期方法以及开源项目 spug 中使 ...

  3. react实战系列 —— 起步(mockjs、第一个模块、docusaurus)

    其他章节请看: react实战 系列 起步 本篇我们首先引入 mockjs ,然后进入 spug 系统,接着模仿"任务计划"模块实现一个类似的一级导航页面("My任务计划 ...

  4. react实战系列 —— 我的仪表盘(bizcharts、antd、moment)

    其他章节请看: react实战 系列 My Dashboard 上一篇我们在 spug 项目中模仿"任务计划"模块实现一个类似的一级导航页面("My任务计划") ...

  5. 七天接手react项目 系列 —— react 脚手架创建项目

    其他章节请看: 七天接手react项目 系列 react 脚手架创建项目 前面我们一直通过 script 的方式学习 react 基础知识,而真实项目通常是基于脚手架进行开发. 本篇首先通过 reac ...

  6. 七天接手react项目 系列 —— react 路由

    其他章节请看: 七天接手react项目 系列 react 路由 本篇首先讲解路由原理,接着以一个基础路由示例为起点讲述路由最基础的知识,然后讲解嵌套路由.路由传参,最后讲解路由组件和一般组件的区别,以 ...

  7. React 实战系列:模块化

    本系列以实战为主,通过一个 TODO 应用来学习深入 React. 学习无捷径,唯一的办法就是 coding!coding!coding! 如有不足之处,欢迎大家批评指正,共同进步! 言毕,开始撸

  8. React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发

    React Native实战系列教程之自定义原生UI组件和VideoView视频播放器开发   2016/09/23 |  React Native技术文章 |  Sky丶清|  4 条评论 |  1 ...

  9. React 深入系列1:React 中的元素、组件、实例和节点

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加深对React的理解,以及在项目中 ...

随机推荐

  1. R-CNN学习笔记

    R-CNN学习笔记 step1:总览 步骤: 输入图片 先挑选大约2000个感兴趣区域(ROI)使用select search方法:[在输入的图像中寻找blobby regions(可能相同纹理,颜色 ...

  2. 关于『HTML5』:第二弹

    关于『HTML5』:第二弹 建议缩放90%食用 咕咕咕咕咕咕咕!!1 (蒟蒻大鸽子终于更新啦) 自开学以来,经过了「一脸蒙圈的 半期考试」.「二脸蒙圈的 体测」的双重洗礼,我终于有空肝 HTML5 辣 ...

  3. python常用标准库(os系统模块、shutil文件操作模块)

    常用的标准库 系统模块 import os 系统模块用于对系统进行操作. 常用方法 os模块的常用方法有数十种之多,本文中只选出最常用的几种,其余的还有权限操作.文件的删除创建等详细资料可以参考官方文 ...

  4. ER图/模型转换为关系模型

    ER图中的主要成分是实体类型和联系类型,转换规则就是如何把实体类型.联系类型转换成关系模式. 1. 二元联系转换 规则1.1(实体类型的转换):将每个实体类型转换成一个关系模式,实体的属性即为关系模式 ...

  5. c++ 辗转相除(动图)

    #include<iostream> #include<cstdio> #include<iomanip> #include<cstring> usin ...

  6. Mac Book安装Windows发烫的问题

    Mac Book安装Windows后,电脑发烫,风扇一直高速旋转.针对此问题百度搜索了一下, 大多数人说更改电源选项,由"平衡"模式改为"节能"模式,亲身体验了 ...

  7. 如何实现Springboot+camunda+mysql的集成

    本文介绍基于mysql数据库,如何实现camunda与springboot的集成,如何实现基于springboot运行camunda开源流程引擎. 一.创建springboot工程 使用IDEA工具, ...

  8. 在jupyter中配置c++内核

    安装 xeus-cling conda install xeus-cling -c conda-forg xeus-cling 是一个用于编译解释于C++的Jupyter内核目前,支持Mac与Linu ...

  9. 第五章、Linux网络服务之yum仓库

    目录 一.yum仓库简介 二.yum配置文件 1yum主配置文件 2日志文件 三.yum命令详解 1查询软件包命令 2查询软件包组命令 3yum安装升级 4 软件卸载 四.搭建yum仓库 本地仓库 h ...

  10. django生成迁移文件和执行迁移的命令

    生成迁移文件: python manage.py makemigrations    #创建数据库迁移文件 执行迁移: python manage.py migrate    # 根据数据库迁移文件生 ...