3

react实战系列 —— react 的第一个组件 - 彭加李

 1 year ago
source link: https://www.cnblogs.com/pengjiali/p/16556882.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

其他章节请看:

react实战 系列

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 迭代没那么困难。

搭建组件的框架

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

<body>
    <div id="root">
        <!-- 此元素的内容将替换为您的组件 -->
    </div>

    <!-- react 库  -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <!-- 用于处理 Dom 的 react 包 -->
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <!-- 使用 PropTypes 进行类型检查 -->
    <script src="https://unpkg.com/[email protected]/prop-types.js"></script>

    <script>
        const reactElem = React.createElement(
            'h1',
            {title: 'i am h1'},
            'Hello, world!'
        )
        ReactDOM.render(
            reactElem,
            document.getElementById('root')
        );
    </script>
</body>

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

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

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

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

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

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

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

React.createElement(
  type,
  [props],
  [...children]
)

Tip:react 元素是什么?

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

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

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

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

React 元素

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

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

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

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

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

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

<div>
    <h2>i am h2</h2>
    <a href="www.baidu.com">go baidu</a>
    <p>
        <em>i am em element</em>
    </p>
</div>

可以这么写:

const c = React.createElement
const reactElem2 = React.createElement(
    'div',
    {},
    c('h2', {}, 'i am h2'),
    c('a', {href: 'www.baidu.com'}, 'go baidu'),
    c('p', {}, 
        c('em', {}, 'i am em element')
    )
)

虚拟 DOM 树

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

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

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

React 组件

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

<script>
    const c = React.createElement
    const reactElem = React.createElement(
        'div',
        {},
        c('h2', {}, 'i am h2'),
        c('a', {href: 'www.baidu.com'}, 'go baidu'),
        c('p', {}, 
            c('em', {}, 'a am em element')
        )
    )
    ReactDOM.render(
        reactElem,
        document.getElementById('root')
    );
</script>

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

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

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

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

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

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

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

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

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

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

<script>
    class MyComponent extends React.Component{
        render(){
            const c = React.createElement
            return React.createElement(
                'div',
                {},
                c('h2', {}, this.props.cnt1),
                c('a', {href: 'www.baidu.com'}, 'go baidu'),
                // class 属性在需要改为 className 
                c('p', {className: this.props.aClass}, 
                    c('em', {}, this.props.cnt2)
                )
            )
        }
    }
</script>
<script>
    // createElement 第一个参数可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型
    const App = React.createElement(MyComponent, {
        cnt1: 'i am h2',
        aClass: 'p-class',
        cnt2: 'a am em element'
    })
    ReactDOM.render(
        App,
        document.getElementById('root')
    );
</script>

生成的 html 如下:

<div>
    <h2>i am h2</h2>
    <a href="www.baidu.com">go baidu</a>
    <p class="p-class">
        <em>a am em element</em>
    </p>
</div>

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

this.props 是怎么来的

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

就像这样:

<script>
    // 父类
    class Rectangle {
        constructor() {
            // 子类接收的参数,这里 arguments 都能接收到
            const args = Array.from(arguments)
            // args= (3) [{…}, 'b', 'c']
            console.log('args=', args)
            this.props = args[0]
        }
    }
    // 子类
    class Square extends Rectangle {
        render(){
            // name= pjl
            console.log('name=', this.props.name)
        }
    }

    let square = new Square({name: 'pjl'}, 'b', 'c')
    square.render()
</script>

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

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

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

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

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

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

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

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

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

<script>
    class MyComponent extends React.Component {
        ...
    }

    // 属性类型
    MyComponent.propTypes = {
        cnt1: PropTypes.number,
        // 注:函数不是 function,而是 func
        // 函数类型,并且必填
        func: PropTypes.func.isRequired
    }
    // 默认值
    MyComponent.defaultProps = {
        cnt1: 'defaultName'
    }
</script>

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

Warning: Failed prop type: Invalid prop `cnt1` of type `string` supplied to `MyComponent`, expected `number`
警告:失败的属性类型:提供给“MyComponent”的类型为“string”的属性“cnt1”无效,应为“number”`

Warning: Failed prop type: The prop `func` is marked as required in `MyComponent`, but its value is `undefined`.
警告:失败的属性类型:属性“func”在“MyComponent”中标记为必需,但其值为“undefined”。

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

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

<script>
    class Rectangle {
        constructor() {
            this.props = arguments[0]
            // 模拟类型验证
            this.validate(this.constructor)
        }
        validate(subClass) {
            Object.keys(subClass.propTypes).forEach(key => {
                const propType = subClass.propTypes[key]
                const type = typeof this.props[key]
                if (type !== propType) {
                    console.error(`Warning: ${key} 属性 - 期待类型是 ${propType},所传入的类型确是 ${type}`)
                }
            })
        }
    }

    class Square extends Rectangle {
        render() {

        }
    }

    Square.propTypes = {
        name: 'string',
        age: 'number'
    }
    let square = new Square({ name: 18, age: 'pjl' })
    square.render()
</script>

类型检测结果如下:

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

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

class Square extends Rectangle {
    static propTypes = {
        name: 'string',
        age: 'number'
    }
    render(){}
}

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

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

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

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

<script>
    class MyComponent extends React.Component {
        render() {
            const c = React.createElement
            return React.createElement(
                'div',
                { className: 'parent-class' },
                c('h2', {}, this.props.cnt1),
                // 核心是:this.props.children。
                this.props.children
            )
        }
    }

    class MySubComponent extends React.Component {
        render() {
            const c = React.createElement
            return React.createElement(
                'div',
                { className: 'sub-class' },
                c('a', { href: 'www.baidu.com' }, 'go baidu'),
                c('p', { className: this.props.aClass },
                    c('em', {}, this.props.cnt2)
                )
            )
        }
    }

</script>
<script>

    // createElement 第一个参数可以是标签名字符串(如 'div' 或 'span'),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型
    const App = React.createElement(MyComponent, {
            cnt1: 'i am h2',
        },
        React.createElement('p', {}, 'i am p element'),
        React.createElement(MySubComponent, {
            aClass: 'p-class',
            cnt2: 'a am em element'
        }
        ))
    ReactDOM.render(
        App,
        document.getElementById('root')
    );
</script>

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

<div id="root">
    <div class="parent-class">
        <h2>i am h2</h2>
        <!-- this.props.children -->
        <p>i am p element</p>
        <div class="sub-class">
            <a href="www.baidu.com">go baidu</a>
            <p class="p-class">
                <em>a am em element</em>
            </p>
        </div>
        <!-- /this.props.children -->
    </div>
</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,一个提交按钮。

代码如下:

<script>
    class MyComponent extends React.Component {
        render() {
            const c = React.createElement
            return React.createElement(
                'div',
                { className: 'parent-class' },
                this.props.children
            )
        }
    }

    class MySubComponent extends React.Component {
        constructor(props){
            // 如果不需要使用 this.props,则可以是 super()
            super(props)
            // 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this
            this.state = {age: 18}
        }
        render() {
            const c = React.createElement
            return React.createElement(
                'form',
                { className: 'sub-class' },
                c('p', { }, 
                    c('input', {type: 'text', value: this.state.age})
                ),
                c('p', { }, 
                    c('input', {type: 'submit', value: 'submit'})
                ),
            )
        }
    }

</script>

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

<div id="root">
    <div class="parent-class">
        <form class="sub-class">
            <p><input type="text" value="18"></p>
            <p><input type="submit" value="submit"></p>
        </form>
    </div>
</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 能提交。

代码如下:

<script>
    class MyComponent extends React.Component {
        render() {
            const c = React.createElement
            return React.createElement(
                'div',
                { className: 'parent-class' },
                this.props.children
            )
        }
    }

    class MySubComponent extends React.Component {
        constructor(props) {
            // 如果不需要使用 this.props,则可以是 super()
            super(props)
            // 在构造函数中访问 this 之前,一定要调用 super(),它负责初始化 this
            this.state = { age: 18 }

            // 自定义的方法,react 没有帮我们处理 this。所以这里需要我们自己绑定一下
            this.handleTextChange = this.handleTextChange.bind(this)
            this.handleSubmit = this.handleSubmit.bind(this)
        }
        handleTextChange(evt) {
            // 不手动绑定 this,则为 undefined
            // console.log('this', this)

            const v = evt.target.value

            // 用来自事件的数据更新组件状态。否则界面是看不到 age 的最新值的
            this.setState({ age: v })
        }
        // React 要阻止默认行为,必须显式的使用 preventDefault
        handleSubmit(evt) {
            evt.preventDefault()
            // 提交表单。state {age: 18}
            console.log('提交表单。state', this.state)
        }
        render() {
            const c = React.createElement
            return React.createElement(
                'form',
                {
                    className: 'sub-class',
                    onSubmit: this.handleSubmit
                },
                c('p', {},
                    c('input', {
                        type: 'text',
                        value: this.state.age,
                        // React 事件的命名采用小驼峰式(camelCase),而不是纯小写。例如在 html 中通常都是小写(onclick)
                        onInput: this.handleTextChange
                    })
                ),
                c('p', {},
                    c('input', { type: 'submit', value: 'submit' })
                ),
            )
        }
    }
</script>

函数传递数据

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

class MyComponent extends React.Component {
    constructor(props) {
        super(props)
        this.handleSubmit = this.handleSubmit.bind(this)
    }
    handleSubmit(data) {
        console.log('提交表单 data=', data)
    }
    render() {
        const c = React.createElement
        return React.createElement(
            'div',
            { className: 'parent-class' },
            React.createElement(MySubComponent, {
                aClass: 'p-class',
                cnt2: 'a am em element',
                onFormSubmit: this.handleSubmit
            })
        )
    }
}

class MySubComponent extends React.Component {
    handleSubmit(evt) {
        evt.preventDefault()
        this.props.onFormSubmit(this.state)
    }
}

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

<div class='componentA'>
    <p class='componentB'> apple </p>
    <button class='componentC'>add apple</button>
</div>

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

全部代码如下:

<script>
    class AComponent extends React.Component {
        constructor(props) {
            super(props)
            this.state = {apples: this.props.apples}
            this.handleAddApple = this.handleAddApple.bind(this)
        }
        handleAddApple(data) {
            this.setState({apples: [data, ...this.state.apples]})
        }
        render() {
            const c = React.createElement
            return c(
                'div',
                { className: 'componentA' },
                this.state.apples.map((item, index) => c(BComponent, {
                    content: item,
                    // 需要增加 key 属性,否则报错:
                    // Warning: Each child in a list should have a unique "key" prop.
                    key: index
                })),
                c(CComponent, {
                    onHandleAddApple: this.handleAddApple
                })
            )
        }
    }

    class BComponent extends React.Component {
        render() {
            const c = React.createElement
            return React.createElement(
                'p',
                {
                    className: 'componentB',
                    // key: this.props.key
                },
                this.props.content
            )
        }
    }

    class CComponent extends React.Component {
        constructor(props) {
            super(props)

            this.handleAdd = this.handleAdd.bind(this)
        }
        
        // React 要阻止默认行为,必须显式的使用 preventDefault
        handleAdd(evt) {
            this.props.onHandleAddApple('apple')
        }
        render() {
            const c = React.createElement
            return React.createElement(
                'button',
                {
                    className: 'componentC',
                    onClick: this.handleAdd
                },
                'add apple'
            )
        }
    }
</script>

<script>
    const App = React.createElement(AComponent, {
            apples: ['apple']
        },
    )
    ReactDOM.render(
        App,
        document.getElementById('root')
    );
</script>

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

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

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

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

<body>
    <div id="root"></div>

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/prop-types.js"></script>

    <!-- Babel 能够转换 JSX 语法 -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
        class AComponent extends React.Component {
            constructor(props) {
                super(props)
                this.state = { apples: this.props.apples }
                this.handleAddApple = this.handleAddApple.bind(this)
            }
            handleAddApple(data) {
                this.setState({ apples: [data, ...this.state.apples] })
            }
            render() {
                return <div className='componentA'>
                    {
                        this.state.apples.map(
                            (item, index) =>
                                (<BComponent content={item} key={index} />)

                        )
                    }
                    <CComponent onHandleAddApple={this.handleAddApple} />
                </div>
            }
        }

        class BComponent extends React.Component {
            render() {
                return <p className='componentB'>{this.props.content}</p>
            }
        }

        class CComponent extends React.Component {
            constructor(props) {
                super(props)

                this.handleAdd = this.handleAdd.bind(this)
            }

            handleAdd(evt) {
                this.props.onHandleAddApple('apple')
            }
            render() {
                return <button className="componentC" onClick={this.handleAdd}>add apple</button>
            }
        }
    </script>

    <script type="text/babel">
        const App = <AComponent apples={['apple']} />

        ReactDOM.render(
            App,
            document.getElementById('root')
        );
    </script>

</body>

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

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

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

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

其他章节请看:

react实战 系列


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK