yinguangyao / blog

关于 JavaScript 前端开发、工作经验的一点点总结。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React 入门:JSX 和组件

yinguangyao opened this issue · comments

commented

1. React 背景

React 最早是 Facebook 的内部项目,当时 Facebook 对市场上的 MVC 框架都不满意,于是自己做了一套新的框架。
在 React 诞生之前,Facebook 内部已经有了 React 的雏形项目。在2010年,Facebook 开源了 XHP 项目,支持在 PHP 中书写模板,这种模板语法和现在 React 中的 JSX 非常相似。

image_1dkb8tqnq5u71g8fmr9u599u49.png-51.3kB

2011年,jordwalke 开源了一个名为 FaxJS 的项目,支持组件化、服务端渲染等等,这是 React 最初的灵感之一。
现在 FaxJS 的 GitHub 上已经建议开发者去使用 React。

This is an old, experimental project: React is much better in every way and you should use that instead. This project will remain on Github for historical context.

2013年,Facebook 将 React 开源,虽然刚开始很多开发者认为这是一种倒退,但随着越来越多人使用,React 获得了越来越多的肯定,现在已经是最流行的前端框架之一。

2. React 的优势

相比传统的 jQuery 和原生 JS,React 带来了数据驱动、组件化的**。

2.1 组件化

前面在讲解模块化的时候,有提到过组件这个概念。如果将应用看做一个玩具,那么组件就是组成这个玩具的一块块积木。

image_1dqcgheqlf23dqea0o1mc6r891g.png-37.2kB

在现代化的前端框架之前,也有过一些组件化的**,比如 jQuery 丰富的插件。但这些大都不够彻底,都只是对逻辑层的封装,很多插件都还需要用户配合写要求格式的 HTML 结构。
举个简单的例子,比如有这么一个 Slider 插件,如果想要使用它,就必须按照插件规定的 HTML 格式来编写,不然它就无法在插件内部准确地获取到 DOM。

<div class="slider">
    <div class="slider-item"></div>
    <div class="slider-item"></div>
    <div class="slider-item"></div>
</div>

$(".slider").start({
    showDot: true,
    animate: true
})

React 中提出了 JSX,可以将 HTML 放到 JS 中编写,将 UI 和 逻辑都封装到了组件中,这样做到了更彻底的组件化。

2.2 数据驱动

在 jQuery 中,用户点击了某个 DOM-A 元素,来修改另一个 DOM-B 中相应的内容。
一般的做法是对这个 DOM-A 绑定事件,之后在事件里面用 $ 来查询获取到 DOM-B,然后修改 DOM-B 的 innerHTML 值。

$("DOM-A").click(function() {
    $("DOM-B").html('xxx');
})

只是这样的确比较简单,如果不仅仅要修改 DOM-B 呢?还要同时修改 DOM-C、DOM-D等等呢?

$("DOM-A").click(function() {
    $("DOM-B").html('xxx');
    $("DOM-C").html('xxx');
    $("DOM-D").html('xxx');
})

如果修改 DOM-B 的来源不仅仅是点击 DOM-A 呢?也可能是点击了 DOM-C 和 DOM-D,这就会让应用中的数据流动很不清晰。

image_1dqcg1g96hc49ku1no31tim1o5r13.png-30.8kB

除此之外,频繁的 DOM 操作会让性能变得比较低,尤其是涉及到 重排 的时候。
React 中则采用了数据驱动的**,那就是我能不能把这个页面中渲染的数据放到某个地方来管理呢?
只要我修改了这个数据,就会引发页面的重新渲染,这样就不需要直接修改页面。
实际上,在前面讲解 MVC 实现的时候,已经带大家实现过这么一个例子,通过 View 对 Model 中的数据进行监听,一旦数据发生变化,View 就会重新渲染。
React 中提供了 state 来管理这些数据。可以参考下面这个 Toggle 组件:

// 只要点击 div 就会修改 state.show 的值,从而触发重新渲染
class Toggle extends React.Component {
    state = {
        show: false
    }
    toggle() {
        this.setState({
            show: !this.state.show
        })
    }
    render() {
        <div class="toggle">
            <div class="notice">click for toggle</div>
            <span>{this.state.show}</span>
        </div>
    }
}

3. 虚拟 DOM

React 中的 DOM 和浏览器中的 DOM 并不一样,它只是基于 JS 对象创造的虚拟 DOM,只有在某个时刻才会将这些虚拟 DOM 渲染到真实 DOM 当中。
下图就是一个虚拟 DOM 的结构:

```

那么虚拟 DOM 有什么有优点呢?

3.1 diff

虚拟 DOM 允许在未插入到真实 DOM 中的时候进行 diff 比较,比较出前后两次更新的差异,之后再插入到真实 DOM 当中,这样会减少频繁的 DOM 操作带来的性能消耗。

3.2 跨平台

由于虚拟 DOM 不依赖于浏览器这个平台实现,可以渲染到多个平台。比如在 Web 当中虚拟 DOM 会被渲染成真实 DOM,在 React Native 中虚拟 DOM 会被渲染成原生的组件,这样就给 React 带来了跨平台的能力,这也是 React 相对于另外几个框架的优势。

4. 准备运行环境

讲了这么多,那么接下来开始进入正题。
在开始开发 React 之前,这里提供了几种方式来运行 React 应用。

  1. Babel REPL
    可以直接在 Babel REPL 中编写代码,实时预览 babel 编译后的结果。

  2. codesandbox
    codesandbox 是一个在线网站,可以直接创建 React/Vue/Typescript 等项目,不需要自己去配置 babel。

  3. 本地(推荐)
    由于 JSX 语法并非浏览器原生支持,因此我们除了引入 react 和 react-dom 两个库之外,还需要引入 babel 来将 JSX 编译为浏览器可以运行的 javascript。
    所以只需要在 html 文件中引入下面这三个 script 标签,并且将 script type 设置为 text/babel 就可以直接在本地编写 React 组件了。

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<script type="text/babel">

const Header = () => <h1>hello, world</h1>
ReactDOM.render(
    <Header />,
    document.querySelector("#app")
)
</script>

5. ReactDOM.render

一般编写 React 的应用,除了需要引入 react 库之外,还需要引入 react-dom 这个库。react-dom 是 react剥离出的涉及 DOM 操作的部分。
react-dom 中最常用的 API 就是 render,在将项目的根组件插入到真实 DOM 节点中时常常要用到。这一步是必要的,不然你的 React 应用就无法挂载到浏览器的 DOM 中。

// index.html
<body>
    <div id="app"></div>
</body>

// App.jsx
const App = () => <h1>hello, world</h1>
ReactDOM.render(
    <App />,
    document.querySelector("#app")
)

6. JSX

JSX 就是 JavaScript XML 的缩写,遵循 XML 的语法规则,语法格式很像模板,但是由 Javascript 实现的。
编写 React 组件的时候,文件名常常以 .jsx 结尾,首字母保持大写。这是为了和原生的 JS 做区分,让别人一眼就看出来这个文件是一个 React 组件。
当然也可以直接用 .js 结尾的文件,这里全部统一为以 .jsx 结尾且首字母大写的格式作为标准。

+ components
    - Header.jsx
    - Footer.jsx
    - Button.jsx
    - index.js

6.1 createElement

由于 JSX 语法并不能被浏览器识别,所以需要 babel 进行编译,将其编译为浏览器可以识别的 JS 代码。

// 编译前
<Counter count={0} />
<h1 className="title">hello, world</h1>

// 编译后
React.createElement(Counter, {
    ref: "counter",
    count: 0
})
React.createElement("h1", {
  className: "test"
}, "hello, world")

可以看到,JSX 代码片段被编译成了 React.createElement 的形式,很像原生的 document.createElement

注意:编译后用到了 React.createElement,这也是为什么即使文件中没有显式调用 React,也需要手动引入 React。

6.2 JSX 语法

JSX 语法类似我们熟悉的 html,允许我们使用尖括号语法来描述组件。

const App = () => {
    return (
        <div id="app">
            <Header />
            <div className="body">
            </div>
            <Footer />
        </div>
    )
}

这里的 div 就是普通的 html 标签,Header 和 Footer 则是 React 组件。
在 React 中,使用一个组件的时候,可以使用自闭合,也可以使用标签闭合,这点儿和 html 依然类似。

// 对于一个Header组件来说,怎样闭合都可以
<Header />
<Header></Header>

由于 JSX 本质上也是 JS,所以保留字 class 和 for 无法在 JSX 直接使用,需要用 className 和 htmlFor 来代替。例如:

const Header = () => <h1 className="header"></h1>
const App = () => {
    return (
        <div>
            <input type="text" />
            <label htmlFor=""></label>
        </div>
    )
}

在 JSX 中使用变量的时候,不管是在属性上面还是元素节点里面,都需要用花括号来包裹。如果不用花括号,就一律当做原始类型的值来处理。

// good
const App = () => {
    const text = 'hello, world'
    return (
        <h1>{ text }</h1> // 输出hello, world
    )
}
// bad
const App = () => {
    const text = 'hello, world'
    return (
        <h1> text </h1> // 输出text
    )
}

花括号中不仅可以存放变量,甚至还可以存放一段 JS 表达式。

// 根据number的值来渲染不同的文本
const App = (props) => {
    const number = 100;
    return (
        <h1>{ number > 100 ? 'big' : 'small' }</h1>
    )
}

在 JSX 中想要实现 if...else 和 for 循环也很简单,只要在花括号里面写入表达式就行了。
在 JSX 中可以使用三目运算符来代替 if...else。


const App = () => {
    const isShowButton = false;
    return (
        <div id="app">
            {isShowButton ? <button></button> : null}
        </div>
    )
}

如果想输出一个列表,那么可以在 JSX 中使用 map 函数。但要切记,当使用循环输出列表的时候,需要给子项设置 key。key 是每个列表项独一无二的标识,每次列表更新的时候,只要 key 没有发生变化,这个当前的虚拟 DOM 就不会被销毁。
至于 key 的作用是什么,后面我们会讲到。

const List = () => {
    const list = [1, 2, 3, 4, 5]
    return (
        <ul>
            {
                list.map((item, i) => {
                    return <li key={i}>{item}</li>
                })
            }
        </ul>
    )
}

如果你想直接设置元素的 style 属性,那么 jsx 要求传给 style 一个对象,这个对象里面的属性和原生 DOM 保持一致,规定为必须是驼峰式的。

// 我们将对象styles传给了style属性,styles里面设置了背景颜色background-color
const Button = (props) => {
    const styles = {
        backgroundColor: props.color
    }
    return (
        <button style={styles}>{ props.text }</button>
    )
}

由于 JSX 本质上也是 JSX,因此在 JSX 里面也可以直接使用 js 的注释。但是要注意,这里一般只能用 /* */ 这种注释。

const App = () => {
    return (
        <p>
            {/* 单行注释 */}
            {
                /*
                多行注释符号
                多行注释符号
                */
            }
        </p>
    )
}

7. 组件

如果你以前有用过 jQuery 的插件,那么一定对这种结构很清楚吧。

function Employee ( name, position, salary, office ) {
        this.name = name;
        this.position = position;
        this.salary = salary;
        this._office = office;
 
        this.office = function () {
            return this._office;
        }
    };
 
    $('#example').DataTable( {
        data: [
            new Employee( "Tiger Nixon", "System Architect", "$3,120", "Edinburgh" ),
            new Employee( "Garrett Winters", "Director", "$5,300", "Edinburgh" )
        ],
        columns: [
            { data: 'name' },
            { data: 'salary' },
            { data: 'office()' },
            { data: 'position' }
        ]
    } );

这是 jQuery DataTable 官网的一个使用例子。DataTable 方法接收了一个对象,这个对象包括 data 和 columns 属性,这两个属性分别代表了这个表格要展示的数据和表格的头部。
这个 DataTable 就是一个组件,这个组件接收了一些数据,输出了对应的界面。

image_1dkq2tlreuhegvhe9bp9bihk9.png-32.1kB

组件化的作用是为了复用相同的界面。比如我们每个页面中都有一个 button 按钮,但每个按钮的颜色都不一样,如果我们在每个页面都编写一个按钮,那么就得不偿失。这时候就应该将按钮封装成一个 Button 组件,根据传入的颜色来展示不同的按钮。

7.1 组件语法

一般来说,React 中的组件有两种,一种是原生组件,即用原生 HTML 标签的组件,一种 React 自定义组件。
原生组件:

const Header = () => <header>hello, world</header> // header就是原生组件

自定义组件:

const App = () => <Header></Header> // 上面创建的 Header 组件就是 React 自定义组件

同时,React 的每个组件必须要有一个根节点,如果有多个根节点就会导致报错,这是因为 React 在创建虚拟 DOM 树的时候,需要有个根节点。
因此,经常会出现不得不在多个 div 外面再套一个 div 的情况,有时候就会使得样式错乱。
所幸的是,React16 之后可以用 React.Fragment 和 <></> 来代替。

// bad
const App = () => {
    return (
        <p></p>
        <p></p>
    )
}
// good
const App = () => {
    return (
        <div>
            <p></p>
            <p></p>
        </div>
    )
}
// good
const App = () => {
    return (
        <>
            <p></p>
            <p></p>
        </>
    )
}
// good
const App = () => {
    return (
        <React.Fragment>
            <p></p>
            <p></p>
        </React.Fragment>
    )
}

7.2 函数组件与类组件

在 React 中,组件的定义方式有两种。一种是纯函数组件,这种组件本质上是一个函数,它接受一个 props,返回一个 view,只是单纯负责展示的,像 Button 就属于这种组件。

// Button组件接收了一个props,返回了对应的button按钮
const Button = (props) => {
    return <button>{ props.text }</button>
}

而另一种组件是类组件,在类组件中会提供更多的功能,比如 state 和生命周期,这种组件允许在内部控制状态,像轮播图、选项卡就属于这种组件。
定义类组件的时候需要继承 React.Component 这个类。

class Calculation extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    handlePlus = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    handleMinus = () => {
        this.setState({
            count: this.state.count - 1
        })
    }
    render() {
        return (
            <div className="calculation">
                <button onClick={this.handlePlus}>+</button>
                <span>{this.state.count}</span>
                <button onClick={this.handleMinus}>-</button>
            </div>
        )
    }
}

可以看下效果:
react入门.gif-86.9kB

我们来一步步去讲解这个例子。
首先,我们在 Calculation 的构造函数中,手动用 super 调用了 React.Component 这个构造函数并传入了 props,这一步是为了初始化组件中的 props。
其次,在构造函数中定义了 state,react 中的 state 是一个对象。在 render 的时候将 this.state.count 展示了出来。
这里对加减两个符号绑定了点击事件。首先要注意,这里的 handlePlushandleMinus 是用箭头函数定义的,这是为了避免 handlePlushandleMinus 中的 this 丢失。
当然,也可以不用箭头函数来定义 handlePlus 和 handleMinus,这里还有其他几种方法,比如使用 bind 函数。

class Calculation extends React.Component {
    constructor(props) {
        this.handlePlus = this.handlePlus.bind(this)
        this.handleMinus = this.handleMinus.bind(this)
    }
    handlePlus() {
    }
    handleMinus() {
    }
    render() {
    }
}

还可以使用双冒号的语法,双冒号相当于 bind,但无法传值。

class Calculation extends React.Component {
    constructor(props) {
    }
    handlePlus() {
    }
    handleMinus() {
    }
    render() {
        return (
            <div className="calculation">
                <button onClick={::this.handlePlus}>+</button>
                <span>{this.state.count}</span>
                <button onClick={::this.handleMinus}>-</button>
            </div>
        )
    }
}

其次,这里通过 this.setState 方法修改了 state 的值。在 React 中,state 的值无法直接修改,只能通过 setState 来修改。
setState 接收一个新的对象或者函数(函数也要返回一个新的对象),最终会合并到现在的 state 中,这一个过程类似 Object.assign(this.state, newState)
在我们每次修改 state 的时候,都会重新执行一次 render 方法,将组件进行重新渲染,因此我们看到每次展示出来的都是最新的 state。

8. props

上面我们举了 jQuery-DataTable 的例子来说明什么是组件。DataTable 接收了 data 和 columns 两个数据,返回了一个表格。
因此,在 React 的设计理念中,view = f(data)。我们给组件传入对应的数据,最后组件返回了我们想要展示出来的 view。
在 React 中,组件也是接收数据,返回一个 view,只不过这个数据叫 props。
我们来设计一个 Button 组件,这个组件可以根据传入的 props 来展示不同的文字和颜色。

const Button = (props) => {
    const styles = {
        backgroundColor: props.bgColor
    }
    return (
        <button style={styles}>
            {props.text}
        </button>
    )
}

那么我们在调用的时候可以直接给Button组件传值。

const App = () => {
    return <Button bgColor="red" text="submit"></Button>
}

在组件内部通过 props 来拿到外部传给这个组件的值,比如上面的 bgColor 值为 'red' 是 App 组件从外部传给 Button 组件的,Button 组件在内部通过 props.bgColor 又拿到了这个背景颜色。
props 除了可以传递值,也可以传递对象、数组甚至函数等等。
比如我们想给 Button 组件绑定点击事件,允许我们在外部调用,那就可以把事先写好的 onClick 函数传入。在点击 button 按钮的时候,会去执行这个 onClick 函数。

const Button = (props) => {
    return (
        <button style={{backgroundColor: props.color}} onClick={props.onClick}>
            {props.children}
        </button>
    )
}
const App = () => {
    const handleClick = (event) => {
        console.log(event.target);
    }
    return <Button color="red" onClick={handleClick}>submit</Button>
}

tip:React 中的 props 一般是不允许直接修改的,直接修改会引发报错。但如果传进来的是个深层对象,也是可以修改引用的。

const App = props => {
    props.obj = {
        name: 'hello, world'
    } // error
    props.obj.name = 'hello, world'; // ok
    return <h1>{props.obj.name}</h1>
}

8.1 children

props 还提供了类似插槽的功能,我们可以在组件内部通过 props.children 获取到组件的子节点。这种渲染方式可以让我们设计出非常灵活的组件。

假设有个轮播图组件,你想它既能展示图片轮播,又能放入一个 div 展示文字,甚至你还想让它展示表格、视频等等,这个组件该怎么来设计呢?

考虑一下,如果只封装这个组件的各种切换状态,而里面的内容可以让使用者自行插入,这样是不是就很完美了?不管用户插入什么内容,我都会原封不动展示出来。

可以参照下方的这个 Slider:

class Slider extends React.Component {
    render() {
        return (
            <div className="slider">{ this.props.children }</div>
        )
    }
}
// 调用方式
<Slider>
    <img src="1.jpg" />
    <Video />
    <Table />
</Slider>

最终,被 Slider 组件包裹着的三个组件最终会被渲染到 this.props.children 的位置。
最终渲染出来的结果如下:

<div className="slider">
    <img src="1.jpg" />
    <Video />
    <Table />
</div>

注意,props.children 有可能是 undefined(没有children),有可能是个对象(只有一个children),也有可能是个数组(有多个children)。这取决于传入的 children 数量。
因此,如果需要遍历 children 的时候,需要注意为另外两种值的可能性。直接遍历对象和 undefined 会导致报错。
React 中提供了 React.Children 这个 API,使用 React.Children.mapReact.Children.forEach 就可以直接遍历了。

function App() {
    return (
        <Slider>
            <h1>hello</h1>
            <p>world</p>
        </Slider>
    );
}
function Slider(props) {
    if (!props.children) {
        return null;
    }
    return React.Children.map(props.children, child => {
        return React.cloneElement(child)
    })
}

9. state

我们在前面讲解类组件的时候已经提到了 state,state 类似一个状态机,可以由一种状态转变为另一种状态。
关于状态机,最形象的理解就是马路上的红绿灯,每隔一段时间可以从红灯切换到绿灯,从绿灯再切换到黄灯,这就是一个典型的状态机。
如果想要深入理解状态机在 JS 中的应用,可以参照一下这篇文章:JavaScript与有限状态机
在 React 中,也是由于 state 的改变,从而引发了组件视图的重新渲染。

9.1 setState

在 React 中,state 的改变只能通过 setState 方法,setState 方法可以接收一个对象或者函数。
当 setState 接收一个对象的时候,那么最后会将当前组件内的 state 和这个对象做合并,返回的新对象作为当前组件内的 state。

切记:不要直接修改 state,即使修改了 state 的属性,组件也不会重新渲染,只会在下一次渲染的时候将你修改过的值合并上去。

以最开始的这个 Toggle 组件为例子。在每次点击执行 toggle 方法的时候,会调用 setState 方法。setState 接收一个对象,这个对象会和最开始的 this.state 对象做一个合并,这个合并类似 Object.assign
如果不考虑 PureComponent 和 shouldComponentUpdate,那么每次执行 setState 都会引发组件的重新渲染,即重新执行一遍 render 函数。

// 只要点击 div 就会修改 state.show 的值,从而触发重新渲染
class Toggle extends React.Component {
    state = {
        show: false
    }
    toggle() {
        this.setState({
            show: !this.state.show
        })
    }
    render() {
        <div class="toggle">
            <div class="notice">click for toggle</div>
            <span>{this.state.show}</span>
        </div>
    }
}

当 setState 接收一个函数的时候,这个函数会返回一个参数,这个参数就是前一次的 state,最终函数会返回一个新的对象,也会将这个新对象和组件内的 state 做合并。

this.setState(function(prevState) {
    return {
        ...prevState,
        count: prevState.count + 1
    }
})

9.2 异步

由于 setState 的设计是“异步”的,所以在 setState 之后一般是无法立刻拿到更新后的值的,最好是使用传入函数的形式。

class Counter extends React.Component {
    state = {
        count: 0
    }
    handleClick = () => {
        this.setState({
            state: this.state.count + 1
        })
        console.log(this.state.count);
    }
    render() {
        return <h1 onClick={this.handleClick}>{this.state.count}</h1>
    }
}

在第一次点击的时候,猜猜这个打印出来的值是多少?也许你会说1,但实际上依然是0。
setState 执行后并没有立即更新 state 的值,而是会将这段时间内多次 setState 进行合并更新,避免了短期内页面的频繁更新。这样带来的问题就是无法立即拿到返回值。
如果想要拿到返回值,那该怎么做呢?其实有很多种方法。
可以利用 setState 的第二个参数,它是一个回调函数,在这个函数里面可以拿到最新的 state。

handleClick = () => {
    this.setState({
        state: this.state.count + 1
    }, () => {
        console.log(this.state.count);
    })
}

还有一种方法,就是利用 setState 可以传入函数的特性。

handleClick = () => {
    this.setState(function(prevState) {
        console.log(prevState.count)
        return {
            count: prevState.count + 1;
        }
    })
}

其实还有其他一些方法,比如 async/awaitPromisesetTimeout 等等,由于涉及到原理层面,这里不进行多说。

关于 setState 的“异步”特性,在后面的文章中会进行深入的讲解。

10. 事件

前面举例子的时候,经常用到 onClick 和 onChange 等事件,那么这些事件和原生事件的区别是什么呢?
首先,在写法上有一定区别,在原生事件中,这些对应的应该是 onclick 和 onchange,但是在 React 中变成了驼峰式的写法。

// 原生 js 的写法
<button onclick="activateLasers()">
  点击
</button>

React 的事件接收到的必须是一个函数,而不是是执行的函数,不然这个函数在事件未触发时就会直接执行了,这点儿和原生函数完全不一样。

<button onClick={activateLasers()}
  点击
</button>

其次,React 中的事件并不是原生事件,而是合成事件。这个合成事件抹平了不同浏览器中的事件差异,本质上都是对 document 节点进行的事件委托。因此,在渲染列表的时候,不需要将事件委托到列表父节点中,因为事件本来就是委托事件了。

事件委托是为了防止在每个列表节点上绑定事件造成的代码冗余,而选择在其父节点上面只绑定一次事件,来通过判断点击时的 target 来区分点击某个子节点的操作。

jQuery 中的事件委托:

$("#list").on("click", ".item", function(event) {
    if (event.target.nodeName === "li") {
    }
})

React 中的合成事件:

class List extends React.Component {
    handleClick = () => {
    }
    render() {
        return (
            <ul>
                {
                    props.list.map(function(item, index) {
                        return <li key={index} onClick={this.handleClick}>{item.text}</li>
                    })
                }
            </ul>
        )
    }
}

注意:在 React 中不建议使用原生事件,除非是对 window 之类的对象进行绑定事件,不然还是优先使用合成事件。

由于 React 中的事件是内部实现的,这也导致了一个问题,那就是事件回调函数中的 this 丢失。一般来说,上述例子中使用箭头函数绑定回调函数是最好的形式,当然你也可以使用 bind。

class App extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick {
    }
    render() {
        return (
            <ul>
                {
                    props.list.map(function(item, index) {
                        return <li key={index} onClick={this.handleClick}>{item.text}</li>
                    })
                }
            </ul>
        )
    }
}

在合成事件的回调函数中一般会返回一个合成事件对象 event,这个 event 有着和原生事件对象类似的 api,比如 target、preventDefault、stopPropagation 等等。

11. 总结

作为当前最火的前端框架之一,React 有着很多优秀的理念和设计。在掌握了基本语法之后,如何设计好的组件更需要我们去不断探索。