React入门

Posted by Wh0ami-hy on August 26, 2024

1. 文章内容

学习 React 的重要概念和相关术语,例如 Babel, Webpack, JSX, components, props, state, lifecycle

最终执行的主要是三大件 html (超文本) + js (脚本) + css (层叠样式表) -> 结构 + 功能 + 样式

入门必读(React 入门引导:理论概述与示例演练 - Snofly)

2. 什么是 React

  • React 是一个 JavaScript 库
  • React 不是一个框架
  • React 是 Facebook 创建的一个开源项目
  • React 被用于在前端构建用户界面(UI)
  • React 是 MVC 应用程序中的视图层(MVC: Model View Controller)

事实上,React 最重要的一点就是,你可以创建组件(components) —— 类似于自定义的、可复用的 HTML 元素,然后借此高效地实现 UI。另一方面,React 通过 state 和 props 大大地简化了数据的存储和处理过程。

3. 使用 React 的方法

这里我将展示其中的两种,以便于你理解到 React 的工作原理

3.1. 静态 HTML 文件

将 JavaScript 库加载到静态 HTML 页面并动态渲染 React 和 Babel

先要创建一个 index.html 文件,然后在 head 元素中加载三个CDN :React、React DOM、Babel。之后要写个 div 元素,设置其 id 为 root,最后创建一个 script 标签用来写你自己的代码。

React 顶层 API React DOM - 提供了一些 DOM 方法 

Babel - 一种 JavaScript 编译器,它能让 ES6+ 代码在旧浏览器上运行。

我们的程序入口是 id 为 root 的 div 元素,这是按照规范约定的命名。同时,你也要注意到 test/babel 这种类型,这是使用 Babel 必须的写法。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />

    <title>Hello React!</title>

    <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
  </head>

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

    <script type="text/babel">
      // React code will go here
    </script>
  </body>
</html>

3.2. Create React APP

可以看到 /public 目录和 /src 目录,还有常规的 node_modules、.gitignore、README.md、package.json。

在 /public 中,最重要的文件是 index.html,它和前文中提到的静态 index.html 文件很像,都是一个 root div。不过这次,没有在文件中加载任何的依赖库和 script 标签。

在 /src 中,将会包含全部的 React 代码。

现在,让我们删除 /src 目录下的全部文件,因为我们将要创建自定义的模板——非常简洁,只有 index.css 和 index.js 文件。

对于 index.css 文件,我就直接复制粘贴来自 Primitive CSS 的内容。当然,如果你想,也可以使用 Bootstrap 或者别的什么 CSS 框架,或者干脆什么都不写。我只是觉得这个更容易使用。

对于 index.js 文件,我们导入 React、ReactDOM,以及 CSS 文件

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";


class App extends React.Component {
    render() {
        return (
            <div className="App">
                <h1>My First React App</h1>
                <p>Hello World!</p>
            </div>
        );
    }
}
ReactDOM.render(<App />, document.getElementById("root"));

4. React Developer Tools

有一个浏览器扩展,叫做 React Developer Tools,它能让你更轻松地使用 React

安装之后,打开浏览器开发工具 F12,你能在 tab 列中看到 React。点击一下,你就能够在编写组件时进行检查。你也可以在 Elements 中查看实际的 DOM 输出。现在看起来用处不大,但是当 app 变得越来越复杂的时候,它就越来越有必要去使用了。

5. JSX

我们在 React 代码中编写的好像是 HTML,但它并不是正经的 HTML,而是 JSX,即 JavaScript XML。React 使用 JSX 来替代常规的 JavaScript。你也可以认为 JSX 其实就是 JavaScript。当遇到 <,JSX就当HTML解析,遇到 { 就当 JavaScript 解析

使用 JSX,我们可以直接编写类似 HTML 的内容(看起来像是HTML和JavaScript的混写),还可以创建和使用自定义的类 XML 便签。下面是 JSX 变量赋值语句。

const heading = <h1 className="site-heading">Hello, React</h1>;

编写 React 代码并不一定要使用 JSX。引擎底层的实现,是通过 createElement 方法来创建标签、属性、子组件的,之后渲染它们。下面的代码具有相同的输出结果。

const heading = React.createElement(
  "h1",
  { className: "site-heading" },
  "Hello, React!"
);

这里有一些编写代码时候需要注意的关键性差异

  • 在 CSS 类名指定时,className 被用来替换 class ,因为在 JavaScript 中 class 是关键字。
  • 在 JSX 中的属性和方法采用小驼峰命名法,比如 onclick 变为 onClick。
  • 自闭合标签必须使用斜杠结尾,例如 < img />
  • JavaScript 代码也能被嵌入 JSX 中,使用花括号{}就能嵌入变量、函数、属性。

JSX 创建和添加元素(大部分)比原生 JS 更容易理解和使用

JSX只能返回单个根元素,但是我们需要返回多个根元素怎么办?

// 错误写法
return(
	   <div></div>
	   <div></div>
	  )

方法一:在这些元素外面再包裹一层元素,使得其变为单个根元素

return(<div>
		   <div></div>
		   <div></div>
	  </div>
	  )

方法二:在这些元素外面再包裹一层空标签,这样渲染结果不会有外部的父级

return(<>
		   <div></div>
		   <div></div>
	  <>
	  )

方法三:<Fragment>标签,和方法二一样,它们都允许你在不添加额外节点的情况下将子元素组合,但是<Fragment> 可以拥有key属性

6. 组件 (Components)

几乎所有 React 组件可以分为两种:类组件(class components)和简单组件(simple components)

index.js

import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

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

APP.js

import React from "react";

class App extends React.Component {
    render() {
        return (
            <div className="App">
                <h1>My First React App</h1>
                <p>Hello World!</p>
            </div>
        );
    }
}
export default App;

我们导出 App 组件,然后在 index.js 中加载。将组件分离到其他文件里面

6.1. 类组件(class components)

现在创建另一个组件,计划是做个表格组件。新建一个 Table.js 文件,我们创建的自定义类组件,它用大写字母开始,以区别于一般的 html 元素

import React from "react";

class Table extends React.Component {
  render() {
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Job</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Charlie</td>
            <td>Janitor</td>
          </tr>
          <tr>
            <td>Mac</td>
            <td>Bouncer</td>
          </tr>
        </tbody>
      </table>
    );
  }
}

export default Table;

6.2. 简单组件(simple components)

目前推荐使用简单组件

React 的简单组件,其本质是一个函数,所以这种组件不需要使用关键字 class

我们使用 ES6 的箭头函数来创建这些简单组件

现在让我们改造 Table,在里面做两个简单组件:一个表格标题、一个表格内容

import React from "react";

const TableHead = () => {
  return (
    <thead>
      <tr>
        <th>Product</th>
        <th>Price</th>
      </tr>
    </thead>
  );
};

const TableBody = () => {
  return (
    <tbody>
      <tr>
        <td>Product 1</td>
        <td>Price 1</td>
      </tr>
      <tr>
        <td>Product 2</td>
        <td>Price 2</td>
      </tr>
    </tbody>
  );
};

class Table extends React.Component {
  render() {
    return (
      <table>
        <TableHead />

        <TableBody />
      </table>
    );
  }
}

export default Table;

注意,TableHeader 和 TableBody 组件在同一个文件中,都是被 Table 类组件使用的。

注意观察,组件是可以被嵌套在其他组件中的,并且简单组件和类组件可以混合使用。

一个类组件必须包含 render() 方法,并且 return 返回的最外层父元素只能有一个

6.3. 类组件和函数组件的区别

/** Simple Component */ 
const SimpleComponent = () => {   
	return <div>Example</div>; 
};
/** Class Component */ 
class ClassComponent extends Component {   
	render() {     
		return <div>Example</div>;   
	} 
}

7. Props

props 和 父子组件通信

  • 子组件通过 props 的属性 获取父组件的数据
  • 父组件通过 props.function 获取子组件的事件等回调

react父子组件通信.jpg

React 的一大优化就是数据的处理,通过属性 props 和状态 state 来实现。接下来,我们将重点关注 props 中的数据处理

将 TableBody 中的数据移除

/** src/Table.js */
const TableBody = () => {
  return <tbody />;
};

把数据都放到一个数组对象里面,就像自定义了一个 JSON 格式参数的 API。这个数组对象需要放在 render() 方法里面

通过 Table 组件的元素特性将数据传到 Table 组件里面,这种传数据的用法类似 HTML5 中的data-*属性

这个元素特性的命名是任意的,只要不与关键字重复即可,所以这里使用自定义的 tbodyData

传递的数据是一个变量 tbodyData,所以需要用花括号包裹起来——其本质上是作为一个 JavaScript 表达式存在的

/** src/App.js */
import React from "react";

import Table from "./Table";

class App extends React.Component {
  render() {
    const tbodyData = [
      {
        Product: "Product 1",

        Price: "100",
      },

      {
        Product: "Product 2",

        Price: "200",
      },
    ];
    return (
      <div className="Table">
        <Table tbodyData={tbodyData} />
      </div>
    );
  }
}

export default App;

现在数据已经从外面传入 Table组件,那么就需要从里面接收数据

/** src/Table.js */
import React from "react";

const TableHead = () => {
  return (
    <thead>
      <tr>
        <th>Product</th>
        <th>Price</th>
      </tr>
    </thead>
  );
};

const TableBody = () => {
  return <tbody />;
};

class Table extends React.Component {
  render() {
    const { tbodyData } = this.props;

    return (
      <table>
        <TableHead />

        <TableBody tbodyData={tbodyData} />
      </table>
    );
  }
}

export default Table;

这时打开 React 开发工具检查 Table 组件,你就能在其 props 中看到数组数据。这些数据是被存储在虚拟 DOM(virtual DOM) 中,这是一种快速同步数据到实体 DOM 方法。

此时数据还没有放到实际的 DOM 中,在 Table 中,我们可以通过 this.props 访问全部的属性。我们使用 ES6 的结构方式来创建一个变量,用于包含 this.props.tbodyData

const { tbodyData } = this.props;

因为 Table 组件包含两个更小的简单组件,所以需要再次通过 props 将数据传递到 TableBody 中

/** src/Table.js */
class Table extends React.Component {
  render() {
    const { tbodyData } = this.props;

    return (
      <table>
        <TableHead />

        <TableBody tbodyData={tbodyData} />
      </table>
    );
  }
}

现在,在 TableBody 中没有参数,并且它也只返回了一个标签。我们将通过函数参数来传递 props,然后通过数组映射来返回构造

const TableBody = (props) => {
  const rows = props.tbodyData.map((row, index) => {
    return (
      <tr key={index}>
        <td>{row.Product}</td>
        <td>{row.Price}</td>
      </tr>
    );
  });

  return <tbody>{rows}</tbody>;
};

在 React 组件中传递存在的数据,props 是一种高效的方式,然而组件不能修改只可读的 props。在下个章节,我们将学习使用状态(state) 来进一步控制 React 中的数据处理。

8. State

  • 不要直接修改 state 的值
  • setState 是一个异步方法
  • setState是新的内容直接换掉旧的内容
  • setState中同名属性,后面的会覆盖前面的

如果我们想要删除数组中的条目呢?通过 props 我们得到了一个单向数据流,通过 state 我们可以更新组件内部的私有数据

state 可以简单理解为一种临时变量

创建一个 state 对象

class App extends React.Component {

    state = {}
}

这个对象中要包含所有你想要存储的变量,这里就是tbodyData

class App extends React.Component {
  state = {
    tbodyData: [
      {
        Product: "Product 1",

        Price: "100",
      },

      {
        Product: "Product 2",

        Price: "200",
      },
    ],
  };
}

这样数据就被包含在 state 中。因为我们想能够移除表格中的数据,所以在 App 中创建一个 removeCharacter 方法。

为了更新 state,我们使用 this.setState(),它是一种操作 state 的内置方法,不能直接写等号赋值。最后基于传入的索引参数过滤数组,获取并返回一个新的数组。

/** src/App.js */
removeTbodyData = (index) => {
  const { tbodyData } = this.state;

  this.setState({
    tbodyData: tbodyData.filter((tbodyData, i) => {
      return i !== index;
    }),
  });
};

在每行的后面渲染一个按钮,点击它可以调用这个函数。这里将 removeCharacter 作为 prop 传递到 Table 中

/** src/App.js */
render() {

	const {tbodyData} = this.state;

	return (

		<div className="Table">

			<Table tbodyData={tbodyData} removeTbodyData={this.removeTbodyData}/>

		</div>

	);
}

还需要将参数从 Table 传递到 TableBody 中,所以再来一次通过 prop 传值

另外,在我们的项目中,只有 App 和 Form 两个组件拥有自己的 state,所以 Table 类组件最好转为简单组件

/** src/Table.js */
import React from "react";

const TableHead = () => {

  return (

    <thead>

      <tr>

        <th>Product</th>

        <th>Price</th>

      </tr>

    </thead>

  );

}

const TableBody = (props) => {

    const rows = props.tbodyData.map((row, index) => {

        return (

            <tr key={index}>

                <td>{row.Product}</td>

                <td>{row.Price}</td>

                <td>

                    <button onClick={() => props.removeTbodyData(index)}>Remove</button>

                </td>

            </tr>

        );

    })

    return(

        <tbody>{rows}</tbody>

    )

}

const Table = (props) =>{

    const {tbodyData, removeTbodyData} = props;

    return(

        <table>

            <TableHead />

            <TableBody tbodyData={tbodyData} removeTbodyData={removeTbodyData}/>

        </table>

    )

}

export default Table;

 观察下面两行代码的区别,onClick 函数必须传入一个函数作为参数,且这个函数是返回的 removeCharacter() 方法,否则,如果直接传入 removeCharacter() 方法给 onClick,则会自动执行一次

<button onClick={() => props.removeCharacter(index)}>Delete</button> <button onClick={props.removeCharacter(index)}>Delete</button>

9. 表单数据提交

我们将数据存储在了 state 中,并且可以移除其中的任意条目。然而,如果我们想要添加新的条目呢?在一个实际的项目中,大概率是从一个空的 state 开始,然后往里面添加数据,例如任务手账或者购物车。

新文件 From.js 中创建一个 Form 组件

将 Form 的初始值设置为空属性对象 initialState,并将其配给 this.state

import React from "react";

class Form extends React.Component {

  initState = {
    Product: "",
    Price: "",
  };
  state = this.initState;
}

我们这个表单的目标是,每当一个表单中的字段发生变化时,能够更新 Form 的 state,然后在提交时,所有数据都要传到 App state 中并更新 Table

首先,创建一个函数,一旦发生了输入改变,它就会执行。这个事件被传递,然后通过设置 Form state 来获得输入的 name(key) 和 value。

/** src/Form.js */
  handleChange = (event) => {

    const { name, value } = event.target;

    this.setState({

      [name]: value,

    });

  };

在 render 中,获取 state 中的两个属性,并将他们作为 value 分配给对应的表单 key。将 handleChange() 作为 input 的 onChange,最后导出 Form 组件。

render() {

const { Price, Product } = this.state;

return (

  <form>

	<label htmlFor="Product">Product</label>

	<input

	  type="text"

	  name="Product"

	  id="Product"

	  value={Product}

	  onChange={this.handleChange}

	/>

	<label htmlFor="Price">Price</label>

	<input

	  type="text"

	  name="Price"

	  id="Price"

	  value={Price}

	  onChange={this.handleChange}

	/>

	<input type="button" value="Submit" onClick={this.submitForm} />

  </form>

);

}
export default Form;

在 App.js 中,我们可以在 表格的下方渲染表单

return (

  <div className="Table">

	<Table tbodyData={tbodyData} removeTbodyData={this.removeTbodyData} />

	<Form handleSubmit={this.handleSubmit} />

  </div>

);

观察前端运行效果。更新表单字段,你会发现本地的 From 也被更新。

最后一步就是提交表单,然后更新父组件状态。我们在 App 组件中创建一个 handleSubmit() 方法,用来更新 App 的 state,数据来自原有的 this.state.tbodyData 以及新添加 bodyItem 参数

handleSubmit = (bodyItem) => {
	this.setState({ tbodyData: [...this.state.tbodyData, bodyItem] });
};

确保把这个函数参数传递到了 Form 中

<Form handleSubmit={this.handleSubmit} />

在 Form 组件中,创建一个 submitForm() 方法,由它来调用 handleSubmit() 并传参,实参就是 Form 中的 state ,形参就是前面定义的 bodyItem。 之后,它会重置 state 为初始值,相当于表单重置

  submitForm = () => {
    this.props.handleSubmit(this.state);
    this.setState(this.initState);
  };

最后,我们添加一个表单提交按钮。注意,这里使用 onClick 而不是 onSubmit,因为我们并不使用标准的提交函数。这个点击事件会调用自定义的 submitForm 函数。

<input type="button" value="Submit" onClick={this.submitForm} />

到此,我们可以创建和删除表格中的数据

10. Pulling in API Data

从 API 中提取数据。如果你不熟悉 API 的概念或者不知道怎么链接 API,我推荐你阅读这篇文章:How to Connect to an API with JavaScript,它会让你知道什么是 API 以及怎样用原生 JavaScript 调用它。

使用 JavaScript 内置的 Fetch 方法从 URL 终端获取数据并展示

11. 构建和部署 React 应用

到目前为止,我们所做的一切都是在开发环境中进行的:编译、热加载、实时更新。 在实际生产中,我们将希望加载的是最后的静态文件,也就是不需要源代码来编译。 这个可以通过构建和部署来实现

现在,如果你想要编译所有的 React 代码,然后把它放到根目录的某个地方,你需要运行这个命令:

npm run build

这会创建一个 build 文件夹来存放你的 app,其中的内容可以放到任何地方,然后就完成了!

我们还可以更进一步,用 npm 将它部署到某处。我们计划创建一个 GitHub 网页,然后将你的代码上传到 GitHub 中。

确保你已经退出本地运行环境,也就是代码没在运行。首先,在 package.json 中添加 homepage 字段,这个 URL 是我们想让这个 app 运行的地址。

/** package.json */ 
"homepage": "https://taniarascia.github.io/react-tutorial",

然后添加这两行到 package.json 的属性 scripts 中。

"scripts": {   
	// ...   
	"predeploy": "npm run build",   
	"deploy": "gh-pages -d build" 
}

在项目中,添加 gh-pages 包来到开发依赖中。

npm install --save-dev gh-pages

我们创建 build 静态文件,它能完成全部的编译。

npm run build

最后部署到 gh-pages 。

npm run deploy

12. 生命周期的钩子方法

  • render
  • constructor 构造器
    • 一般做初始化数据获取操作等
  • componentWillMount 即将做初始化 render 前
  • componentDidMount 组件初始化渲染完成
    • 这里可以操作 dom 节点、获取已渲染 dom 节点宽高等等
    • 可以做些数据判断确认一些初始化数据更新
    • 绑定一些事件监听
  • componentWillReceiveProps(nextProps) 组件接收到新的 props
    • 公共组件做数据判断、同步 props 到 state 等操作
  • shouldComponentUpdate(nextProps, nextState) 是否要更新渲染
    • 可以做性能优化,虽然很鸡肋,搞不好还不如不优化
  • componentDidUpdate(preProps, preState) 组件完成渲染更新
    • 这里可以操作 dom 节点、获取已渲染 dom 节点宽高等等
  • componentWillUnmount 组件即将卸载
    • 解绑一些事件监听

13. children

在子组件中接收,父组件下子组件标签范围内的内容

// 子组件
function List({children}) {
    return (
     < ul > {children} < /ul>
     )
}
// 父组件
export default function App(){
	return (
	<>
		<List><li>列表内容</li > </List>
	</> )
}

子组件中的children将接收到如下内容

 <li>列表内容</li>

14. 区分

我们需要区分两个概念,「DOM 结构」「React 的层级结构」。 就像我们需要区分 「组件渲染」「DOM 变更」 是两件事

  • 操作方式:
    • DOM: 直接通过 JavaScript 操作。
    • React: 声明式,通过组件状态和属性管理。
  • 性能:
    • DOM: 直接操作可能影响性能。
    • React: 通过虚拟 DOM 优化更新。
  • 结构:
    • DOM: 静态树状结构。
    • React: 组件树,动态且可组合。

15. Hook

15.1. useState

实现向组件添加一个状态变量

const [state, setState] = useState(initialState)

15.2. useEffect

实现将组件与外部系统同步

useEffect(setup, dependencies?)

15.3. useContext

用于小的全局状态管理

useRef


本站总访问量