React基础

此随笔同步在我的个人博客,欢迎访问~

什么是组件

简单来说,组件就是页面上的一部分。一个页面由多个组件构成。多个组件共同渲染出一个页面。
react中的组件.png

想让一个组件往页面上渲染内容,需要在这个组件中写一个render()函数

react渲染页面的流程:

index.html --> 执行index.js --> index.js中引入组件 --> 组件通过render()函数来定义需要渲染的内容 --> 组件在index.js中被引入,使用ReactDom.render()挂载到index.html中

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <title>Orla React</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
// index.js
import React from "react";
import ReactDOM from "react-dom";
// 引入组件
import App from "./App";
import Test from "./Test";

// ReactDom.render在渲染的时候只能渲染单标签
ReactDOM.render(
  // JSX语法
  // 想要渲染多个标签,使用JSX语法,将多个标签放入一个div中即可
  <div>
    <App />
    <Test />
  </div>,
  // 将App组件的内容挂载到index.html页面id为root的节点上
  document.getElementById("root")
);
// App.js 定义App组件
import React, { Component } from "react";

// 使用class定义了一个组件
// 要往页面上渲染的内容需要写在render()函数中
class App extends Component {
  render() {
    return <div>Hello World</div>;
  }
}
// function App() {
//   return <div>hello world</div>;
// }

export default App;
// Test.js 定义Test组件
import React, { Component } from "react";

// 使用class定义了一个组件
// 要往页面上渲染的内容需要写在render()函数中
class Test extends Component {
  render() {
    return <div>Test组件</div>;
  }
}

export default Test;

页面效果:

React初识组件App_Test.png

JSX语法

React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

JSX语法中,有两种类型的标签:

  • 普通的html标签(div...)
  • 组件标签(比如上面的<App />),首字母一定要大写

使用React编写ToDoList

下面的代码都是基于TodoList进行编写的。使用代码需要预先创建React项目。可以使用React官方提供的脚手架工具create-react-app快速创建一个React项目(需要nodejs环境)。命令如下:

$ npx create-react-app my-app
$ cd my-app
$ npm start
$ npm install

如上命令创建了一个名为my-app的项目。使用该项目的目录结构即可。

TodoList的功能:

  1. 输入文字,按下回车,添加对应内容至列表中

  2. 点击列表项能够删除该项

    实现效果如下:

    ReactTodoList效果.gif

页面结构

首先在页面中实现一个静态的input框和ul列表。

需要注意的是,render一次只能渲染一个外部标签, 想要渲染多个标签,同时不想用div包裹起来导致dom会新增无用的div标签,可以使用React中提供的Fragment标签。

// index.js
import React from "react";
import ReactDom from "react-dom";

// 引入组件
import TodoList from "./TodoList";

ReactDom.render(<TodoList></TodoList>, document.getElementById("root"));
              
// TodoList.js, TodoList组件,组件开头字母大写
// 引入Fragment组件
import React, { Component, Fragment } from "react";

class TodoList extends Component {
  render() {
    // render一次只能渲染一个外部标签,想渲染多个,需要将标签放在一个div中。
    // 但是这样的做法会导致dom中多出来一个div
    // React16中提供了占位符组件Fragment
    // 将所有组件放在Fragment中,不会在dom中添加其他的元素
    return (
      <Fragment>
        <input />
        <ul>
          <li>Learn React</li>
          <li>Learn Component</li>
        </ul>
      </Fragment>
    );
  }
}

export default TodoList;

React中数据驱动的设计思想和事件绑定★

React是一个数据驱动的框架,所有功能都不直接操作DOM,而是直接操作数据。

TodoList中的数据分为两部分:input中的数据和ul列表中的数据。需要在代码中把这两种数据定义出来。

在组件中定义数据,要在render()函数上面写constructor()函数。constructor中接收参数props,并且要调用父类的构造函数,将props传递给父类的构造函数。所以要固定写super函数。

  // 定义组件中的数据
  constructor(props) {
    // 接收参数props,传递给基类(Component)的构造函数
    super(props);
  }

前面提到,TodoList中的数据分为两部分:input中的数据和ul列表中的数据。我们要在constructor函数中,把这两种数据定义出来。

React中,定义组建的数据要放在this.state中,以对象的形式进行定义。

constructor(props) {
    // 接收参数props,传递给基类(Component)的构造函数
    super(props);
    // React中,定义组件的数据要放在this.state中
    // inputValue定义input输入框中的数据
    // list定义ul列表中的数据
    this.state = {
      inputValue: "hello World",
      list: [],
    };
  }

定义好数据后,将数据放到要渲染的标签中即可。使用花括号将其包裹起来。

  render() {
    return (
      <Fragment>
        {/* 使用花括号将数据包裹起来 */}
        <input value={this.state.inputValue} />
        <ul>
          <li>Learn React</li>
          <li>Learn Component</li>
        </ul>
      </Fragment>
    );
  }

页面显示效果如下:

React数据驱动_1_.png

但是这样做会导致一个问题。input框中的value是固定的,在页面中不能修改输入框中的内容。想要能够动态修改框中的内容,需要在input上绑定一个onChange函数(注意和原生js中onchange不同,此处C要大写)

  // 此处这样直接绑定handleInputChange会导致问题,后续会介绍
  render() {

    return (
      <Fragment>
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <ul>
          <li>Learn React</li>
          <li>Learn Component</li>
        </ul>
      </Fragment>
    );
  }

在组件对应的类中添加handleInputChange函数

handleInputChange(e) {
    console.log(e.target.value);
  }

这里的e.target指的是这个事件绑定的input元素,e.target.value指的就是input输入框中的内容

到这里,看起来好像直接修改this.state中inputValue的值就可以了:

  handleInputChange(e) {
    this.state.inputValue = e.target.value;
  }

不幸的是,输入框中的内容依然不能修改……

React中如果想要修改组件中定义的数据,需要使用setState()方法

  handleInputChange(e) {
    // 数据不能直接改变
    // this.state.inputValue = e.target.value;
    // 要使用setState方法来改变组件中的数据
    this.setState({
      inputValue: e.target.value,
    });
  }

现在setState()也用了,总该没问题了吧。保存!刷新页面!修改输入框中的内容!

又报错了_

React绑定事件不改变this指向.png

呀?他说不能获取undefined的setState……我这个setState可是绑定在this上的……那我看看这个this到底是什么东西。打印一下!

  handleInputChange(e) {
    console.log(this);	// undefined
    this.setState({
      inputValue: e.target.value,
    });
  }

这个this居然是undefined……

看一下我们是如何绑定this的

  render() {
    return (
      <Fragment>
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange}
        />
        <ul>
          <li>Learn React</li>
          <li>Learn Component</li>
        </ul>
      </Fragment>
    );
  }

onChange={this.handleInputChange}中的this是render函数自身的this,指向组件实例。

此时this.handleInputChange只是对于handleInputChange的一个引用,没有加()进行调用,所以在onChange事件触发时才会调用handleInputChange()方法。

此时调用这个方法的应该是全局的window对象,this应该指向window,但是ES6中的class是严格模式,所以这时的this指向的就是undefined。

想要将this绑定到当前的组件实例上,需要使用bind来改变this的指向。于是修改代码如下:

  render() {
    return (
      <Fragment>
        {/* 使用花括号将数据包裹起来 */}
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange.bind(this)}
        />
        <ul>
          <li>Learn React</li>
          <li>Learn Component</li>
        </ul>
      </Fragment>
    );
  }
}

onChange={this.handleInputChange.bind(this)}使this指向了当前的组件实例,再修改当前组件中的数据就可以了。

此时我们就可以随意修改输入框中的数据了~

ReactTodoList修改输入框数据.gif

实现TodoList新增删除功能

处理完了输入框的输入问题,现在来解决列表新增删除的问题。

新增

之前我们在组件的state中定义了两种数据:input输入框的输入数据和<ul>列表的数据。现在默认输入框内容为空,list中存放原本写在<li>标签中的数据。页面上展示的列表应该由list中的内容来决定。

constructor(props) {
    super(props);
    this.state = {
      inputValue: "",
      list: ["Learn React", "Learn Component"],
    };
  }

现在我们希望根据list中的内容循环显示出<li>标签,把它们渲染到页面上。

使用ES6中的map()方法对数组进行循环。map()方法接收一个函数作为参数,这个函数可以接收几个值:value(数组每一项的值),index(每一项的索引值)。使用map进行循环,每一次循环都需要return出一个结果。

对于循环的每一个子项都应该有一个key值,这个key值对于每一个循环都应该是唯一的。添加key值会使react的性能更高。

  render() {
    return (
      <Fragment>
        {/* 使用花括号将数据包裹起来 */}
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange.bind(this)}
        />
        <ul>
          {this.state.list.map((value, index) => {
            return <li key={index}>{value}</li>;
          })}
        </ul>
      </Fragment>
    );
  }
}

此时list数组中的内容便能循环渲染到页面上了。

我们现在希望能够动态地添加列表项。当我们在输入框中按下回车时,将输入框中的内容添加到列表项中。为input标签添加onKeyUp事件,当按下enter键时,修改list数组中的数据即可。

  handleKeyUp(e) {
    // 回车键的键盘码是13,按下回车代表输入完毕,添加列表项
    // 由于函数handleInputChange,this.state.inputValue的值会随着输入框中内容的改变动态改变
    // 获取this.state.inputValue就是获取输入框当前内容
    // 添加后自动清空输入框
    if (e.keyCode === 13) {
      const list = [...this.state.list, this.state.inputValue];
      this.setState({
        list: list,
        inputValue: "",
      });
    }
  }

完成!效果如下:

ReactTodoList添加项.gif

删除

现在我们希望点击某一列表项,能够删除该项。思路很简单,给li标签添加onClick事件,绑定删除的函数即可。

在删除的时候,如何能够知道要删除的是哪一项?我们之前遍历数组的时候使用了map()map()方法中,对于数组每一项都有一个唯一的索引index,根据index值找到要删除的那一项,然后使用splice()方法即可~

  render() {
    return (
      <Fragment>
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange.bind(this)}
          onKeyUp={this.handleKeyUp.bind(this)}
        />
        <ul>
          {this.state.list.map((value, index) => {
            return (
              <li key={index} onClick={this.handleItemClick.bind(this, index)}>
                {value}
              </li>
            );
          })}
        </ul>
      </Fragment>
    );
  }
  handleItemClick(index) {
    const list = [...this.state.list];
    list.splice(index, 1);
    this.setState({
      list,
    });
  }

更多JSX的语法细节

bind(this)的优化

在上面的代码中,每次监听事件都要调用bind(this),这样导致页面中到处都是bind()。而且每次使用bind(this),都会重新生成一个新的函数,也就是说,每次触发事件,都会生成一个新的函数,这样会导致性能降低。

解决这个问题,可以将this的绑定放在constructor中

constructor(props) {
    super(props);

    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleKeyUp = this.handleKeyUp.bind(this);
    
    this.state = {
      inputValue: "",
      list: ["Learn React", "Learn Component"],
    };
  }

这样,监听事件的时候就不需要再调用bind(this)了。

  render() {
    return (
      <Fragment>
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange}
          onKeyUp={this.handleKeyUp}
        />
        <ul>
          {this.state.list.map((value, index) => {
            return (
              <li key={index} onClick={this.handleItemClick.bind(this, index)}>
                {value}
              </li>
            );
          })}
        </ul>
      </Fragment>
    );
  }

上面对onChange和onKeyUp进行了优化。对于onClick,因为每次需要传一个参数index进去,所以没有办法进行改写。

JSX的简化

之前我们直接在JSX中对数组进行循环,这样显得有些臃肿,我们可以把这部分提取出来,封装到一个函数中,然后直接在JSX中调用这个函数即可。

  // 封装为函数
  getListItems() {
    return this.state.list.map((value, index) => {
      return (
        <li key={index} onClick={this.handleItemClick.bind(this, index)}>
          {value}
        </li>
      );
    });
  }

完整TodoList代码

注意:使用代码需要提前创建React项目。

public/index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <title>Todo List</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

src/index.js:

// index.js
import React from "react";
import ReactDom from "react-dom";

// 引入组件
import TodoList from "./TodoList";

ReactDom.render(<TodoList></TodoList>, document.getElementById("root"));

src/TodoList.js:

// TodoList组件,组件开头字母大写
// 引入Fragment组件
import React, { Component, Fragment } from "react";

class TodoList extends Component {
  // 定义组件中的数据
  constructor(props) {
    // 接收参数props,传递给基类(Component)的构造函数
    super(props);

    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleKeyUp = this.handleKeyUp.bind(this);
    // React中,定义组件的数据要放在this.state中
    // inputValue定义input输入框中的数据
    // list定义ul列表中的数据
    this.state = {
      inputValue: "",
      list: [],
    };
  }

  handleInputChange(e) {
    // 这个函数的this默认是undefined
    // 想要让this指向这个组件,需要在render函数中调用此方法时使用bind(this)
    // console.log(e.target.value);
    // 数据不能直接改变
    // this.state.inputValue = e.target.value;
    // 要使用setState方法来改变组件中的数据
    // console.log(this);
    this.setState({
      inputValue: e.target.value,
    });
  }

  handleKeyUp(e) {
    // 回车键的键盘码是13,按下回车代表输入完毕,添加列表项
    // 由于函数handleInputChange,this.state.inputValue的值会随着输入框中内容的改变动态改变
    // 获取this.state.inputValue就是获取输入框当前内容
    // 添加后自动清空输入框
    if (e.keyCode === 13 && e.target.value.trim() !== "") {
      const list = [...this.state.list, this.state.inputValue.trim()];
      this.setState({
        list: list,
        inputValue: "",
      });
    }
  }

  handleItemClick(index) {
    const list = [...this.state.list];
    list.splice(index, 1);
    this.setState({
      list,
    });
  }

  getListItems() {
    return this.state.list.map((value, index) => {
      return (
        <li key={index} onClick={this.handleItemClick.bind(this, index)}>
          {value}
        </li>
      );
    });
  }

  render() {
    // render一次只能渲染一个外部标签,想渲染多个,需要将标签放在一个div中。
    // 但是这样的做法会导致dom中多出来一个div
    // React16中提供了占位符组件Fragment
    // 将所有组件放在Fragment中,不会在dom中添加其他的元素
    return (
      <Fragment>
        {/* 使用花括号将数据包裹起来 */}
        <input
          value={this.state.inputValue}
          onChange={this.handleInputChange}
          onKeyUp={this.handleKeyUp}
        />
        <ul>{this.getListItems()}</ul>
      </Fragment>
    );
  }
}

export default TodoList;

posted @ 2021-03-25 17:06  灯火十里  阅读(77)  评论(0)    收藏  举报