掌握React(二)-- 创建组件

admin 2023-05-13 07:32:03 2104

本文覆盖基本React组件的知识,是创建健壮React应用的基础,包括创建组件、管理状态、通过props传递数据以及调试React等。

 


 

创建一个列表组


先安装bootstrap,它是一个css库,我们用它代替自带的css。

npm i [email protected]

删除App.css里的全部内容。

删除index.css文件,修改Main.tsx,导入bootstrap的css.

import 'bootstrap/dist/css/bootstrap.css'

我们发现网页有所变化,比如Hello World的显示位置已在左上角,字体也不一样了,说明bootscrap的css起作用了。

下面我们来创建ListGroup组件。

在src文件夹下新建components文件夹,再在其下新建ListGroup.tsx文件。

function ListGroup(){
    return <h1>List Group</h1>
}

export default ListGroup;

再到App.tsx,我们不再用Message组件了,删除它,再导入ListGroup。

import ListGroup from "./components/ListGroup"
function App(){
  return <div><ListGroup /></div>
}

export default App

页面上现在已显示 List Group了。

为了显示效果,我们要去bootstrap借用一些代码。

访问https://getbootstrap.com,再点 'Docs',再在左侧栏Components分类里找到List group,选择'Basic Example'并拷贝 html代码,粘贴到ListGroup组件中。我们要将这些html示例代码中的'class'改成’className'(按ctrl + d可以选择多行同样的内容,方便批量修改),因为class是Javascript和Typescript中的保留字,不能在jsx中使用。

function ListGroup(){
    return (
        <ul className="list-group">
            <li className="list-group-item">An item</li>
            <li className="list-group-item">A second item</li>
            <li className="list-group-item">A third item</li>
            <li className="list-group-item">A fourth item</li>
            <li className="list-group-item">And a fifth one</li>
        </ul>
  )
}

export default ListGroup;

网页上List Group变成了与bootstrap上的一致了。

目前列表项是硬编码的,有点乏味,下一节继续。


 

打包多个分段

我们需要在列表组添加标题,直接加上 <h1>List</h1>将会出错。原因是现在有两并列的标记 h1 和 ul,React的jsx最终是要转换成javascript代码的,不能同时返回两个,所以我们要把它用一个标记包起来。

...
            <h1>List</h1>
            <ul className="list-group">
                <li className="list-group-item">An item</li>
                ...
            </ul>
...

最常规的是用div包起来,但为了包起来,就增加一个DOM元素div,没必要;一般是选择Fragment包起来。

function ListGroup(){
    return (
        <Fragment>
            <h1>List</h1>
            <ul className="list-group">
                ...
            </ul>
        </Fragment>
  )
}

还有一种更好的,就是直接用空内容的尖括号,React会自动使用Fragment包起来。

return (
        <>
            <h1>List</h1>
            <ul className="list-group">
                ...
            </ul>
        </>
  )

渲染列表

我们将列表项改成动态生成的。首先定义一个数组items,然后显示在列表项中。在React中的JSX中,是不能使用 for ...in 的语法来循环生成列表项,我们使用了数组的map方法,映射每个表项为另一个类型的表项,map里使用箭头函数,这里我们映射成 <li>{item}</li>。前面我们说过,用大括号将表达式包起来,这样我们就能自动生成表项。

 

在jsx语法中,要么是html格式,要么是组件,这里的items.map(...)是不受识别的,于是在它的外面也加了大括号。

const items =   ["New Youk", "San Francisco", "Tokyo", "London", "Paris"];
function ListGroup() {
  return (
    <>
      <h1>List</h1>
      <ul className="list-group">
        {items.map((item) => (
          <li className="list-group-item">{item}</li>
        ))}
      </ul>
    </>
  );
}

export default ListGroup;

保存后,网页上已显示各城市的列表项了。不过我们在浏览器上按F12检查,发现有警告信息 ‘Each child in a list should have a unique "key" prop’.

这是怎么回事呢?

 

React需要跟踪每个页面上的元素,以便可以更新DOM,在使用列表时,每个列表项必须提供 key 属性用以标识。在实际场景中一使用ID来标识,这里我们就简单使用key={item}的值。保存,警告消失了。

<li className="list-group-item" key={item}>
            {item}
          </li>

条件渲染

有时候我们需要根据条件渲染不同的内容。比如列表项空的话,就显示“No item found”。一种简单的方法是加上判断。

...
  if (items.length === 0)
    return (
      <>
        <h1>List</h1>
        <p>No item found</p>
      </>
    );
  return (
    <>
      <h1>List</h1>
      <ul className="list-group">
      ...

<h1>List</h1>有点重复,考虑把判断逻辑移到jsx语句中,不过在jsx中不允许出现 if 等判断语句,我们使用三元表达式 {items.length === 0 ? <p>No item found</p> : null} 修改如下:

<h1>List</h1>
      {items.length === 0 ? <p>No item found</p> : null}
      <ul className="list-group">
      ...

有时候,这种逻辑语句也会污染jsx的,我们可以把这个语句移出jsx外,返回变量,再在jsx使用。

const message = items.length === 0 ? <p>No item found</p> : null;
  return (
    <>
      <h1>List</h1>
      {message}
      ...

如果需要传参数显示不同的消息,可以使用函数。

const getMessage = () => (items.length === 0 ? <p>No item found</p> : null);
  return (
    <>
      <h1>List</h1>
      {getMessage()}
      ...

我们再看一种比三元表达式更简洁的用法,可以省去后面的null 关键字,这是React开发中的常见用法。我们要学会使用。

{items.length === 0 && <p>No item found</p>}

处理事件

每一个元素都可以点击,这个事件叫做onClick,我们可以添加处理代码,比如在控制台显示'Clicked'.

...
          <li
            className="list-group-item"
            onClick={() => console.log("Clicked")}
            key={item}
          >
...

在网页上点击就能在控制台看到 Clicked 字样,说明已捕捉并处理了点击事件。我们可以处理点击事件输出对应的内容。map方法里的箭头函数除了item还有index,我们让点击直接输出这些内容。

        {items.map((item, index) => (
          ...
            onClick={() => console.log(item, index)}
            ...
        ))}

另外,onClick的处理箭头函数还可以携带参数,表示点击时的关联信息,我们可以传入,命名为event,你可以命名为e 都可以。

onClick={(event)=>console.log(evenv)}

我们点击列表项时,就输出了携带的事件信息。它是syntheticBaseEvent类,非常花哨的名称。各个浏览器的事件有所不同,SyntheticBaseEvent是包装了各浏览器点击事件的类,该类有ClientX,ClientY表示点击的位置,以及其他很多属性。

目前我们的事件处理程序用箭头函数表示并直接放置在JSX语句中,只有一行的简单逻辑是非常好,但如果事件处理程序比较复杂,再放在JSX中就不合适了,我们将处理程序提取出来放在独立的函数,比如:

const handleClick = (event)=>console.log(event)

但我们看到,这里有个警告:

Parameter 'event' implicitly has an 'any' type.ts(7006)

意思是event参数可以是任何类型,这在Typescript中是不允许的,这也是typescript的安全代码特色,更利用重构。

在JSX中的事件处理函数会自动识别event的类型,不会有警告。我们把鼠标移到上面,能看到是提示该事件是 React.MouseEvent。所以在独立函数中,我们要指定event的类型为MouseEvent。同时在JSX中onClick中引用hanleClick,注意仅是引用不是调用,不加括号。

import { MouseEvent } from "react";
function ListGroup() {
  ...
  const handleClick = (event: MouseEvent) => console.log(event);
  return (
   ...
          <li className="list-group-item" onClick={handleClick} key={item}>
   ...
  );
}

管理状态

我们希望选中的列表项高亮显示,高亮显示只需要增加 “active” 的className即可。现在要做的是能判断哪一项是选中的,然后给选中项的css的class添加 "active“。

 

先添加一个变量 selectedIndex,初始等于 -1 ,表示未选中,列表中index中0表示第1项。再在onClick事件处理函数中,我们修改selectedIndex为当前选择项。我们看看能否达到目标。

...
function ListGroup() {
...
  let selectedIndex = -1;
  return (
    ...
            className={
              selectedIndex === index
                ? "list-group-item active"
                : "list-group-item"
            }
            onClick={() => (selectedIndex = index)}
            ...
  );
}
...

但在页面上点击列表项,没什么变化,不会变成高亮显示。这是为什么呢?

 

原因是 selectedIndex只是我们在Component里定义的一个局部变量,React并不会注意它。我们需要告诉React,我们的组件有随着时间改变的数据或状态。这就要用到React的一个内建函数 :useState。

 

useState函数是一个钩子。钩子允许我们进入一些React内建的功能,也有其他的一些钩子,以后会介绍到。useState是个状态钩子,它告诉React有数据或状态,随着时间的推进,这些状态会变化。

 

代替前面的设置变量,我们调用useState,并初始化状态值。useState返两个元素,第一个是变量,比如 selectedIndex;第二个是更新函数比如setSelectedIndex,该更新函数可以更新变量,同时也告诉React组件状态已改变,然后React重新渲染组件。前面我们讲过,React不会直接处理DOM,我们可以理解成组件有状态,当状态变化时,React更新DOM以适配新的状态。

# 先导入
import {useState} from 'react'
...
  const   [selectedIndex, setSelectedIndex] = useState(1);
  return (
    ...
            onClick={() => (setSelectedIndex(index))}
            ...

这样网页就能正确高亮显示选中项了。

 

另外,每一个组件的状态都是独立的,比如我们在App中再添加一个ListGroup组件,在页面上分别点击,就会发现他们的高亮显示是互相独立的。

 


 

通过Props传递数据

目前的ListGroup的列表项是硬编码的,如果想重用ListGroup组件的话,我们就需要传递数据。需要传递 什么数据呢?一个是列表项 items,另一个是标题 heading。Typescript使用Interface定义数据结构,我们创建一个并命名为Props,有些人可能喜欢加前缀的命名如ListGroupProps,这都是可以的。注意typescript 的使用,在定义数组items或字义变量heading时都指定了数据类型,有助于我们及早在编译时而不是运行时才发现错误。

interface Props {
  items: String  [];
  heading: String;
}

修改后的ListGroup.tsx如下,我们传入ListGroup一个Props的数据结构,为了简洁,我们直接解构:{ items, heading }: Props 这样代码就可以直接用这两个变量名。

interface Props {
  items: String  [];
  heading: String;
}
function ListGroup({ items, heading }: Props) {
  ...
      <h1>{heading}</h1>
      {items.length === 0 && <p>No item found</p>}
      <ul className="list-group">
        {items.map((item, index) => (
          ...

现在我们到App.tsx传递数据给ListGroup。先定义items,然后在使用ListGroup提供items和heading的数据即可实现传递。

function App() {
  let items =   ["New Youk", "San Francisco", "Tokyo", "London", "Paris"];
  return (
    <div>
      <ListGroup items={items} heading="cities" />
    </div>
  );
}

完成后,测试页面正常。

 


 

通过Props传递函数

在实际场景中,我们点击选择一个列表项,会有一些操作发生,比如筛选列表,或引导用户进入另一个页面等等。如果直接在ListGroup组件中实现相关操作,将导致ListGroup不可重用,我们要将列表选择这个消息通知客户或者说父级组件,由父级组件来处理,在我们这里,就是通知App组件。这样ListGroup组件就可以重用了。那如何做呢?

 

很简单,先在ListGroup组件的Props中添加第三个属性onSelectItem指定处理函数的格式。

interface Props {
  items: String  [];
  heading: String;
  onSelectItem: (item: String) => void;
}

再在onClick中调用 onItemSelected。

onClick={() => {
              ...
              onSelectItem(item);
            }}

然后在App组件中调用ListGroup时提供处理函数。一般以handle开头,比如这里就是handleItemSelected.该处理函数简单输出对应的列表项条目。

const handleSelectItem = (item:String) => {
    console.log(item);
  };
  return (
    <div>
      <ListGroup
        ...
        onSelectItem={handleSelectItem}
      />
    </div>
  );
}

完成后,网页上点击列表项,就能在控制台输出列表项。

 


 

State和Props对比

Props是外部传递给组件的,而State则是组件自己管理的。

Props像函数的参数,而State像本地变量。

不要改变Props,比如我们不应该在ListGroup中重新设置heading='',在React中,我们要遵守它的哲学,尽管程序不会提示说不能修改。而State本身就是为了改变而存在的。

 

两者也有共同点,它们的改变都会让React重新渲染页面。

传递子项

组件包裹的部分,称之为子项,比如有一个组件Test,我们调用它:<Test>Hello World</Test>,这里的Hello World就是子项,Test组件可以接收这个子项信息,从而进行相关处理。如何做?

 

创建一个新组件 components/Alert.tsx,在键入代码之前我们安装一个vscode插件: ES7+ React/Redux/React-Native snippets。该插件可以快速生成一些React代码片段,免得手输。

在Alert.tsx中,输入 rafce,然后回车,就自动生成了一个由箭头函数形成的Alert组件。我们再作下修改,添加interface,键值使用children,这是一个保留关键字,表示传递的是子项。为了使显示美观,到getbootstrap网站查找css,加了 "alert alert-primary".

import React, { Children } from "react";
interface Props {
  children: String;
}

const Alert = ({ children }: Props) => {
  return <div className="alert alert-primary">{children}</div>;
};

export default Alert;

再到App.tsx,为了演示清晰,将原来的ListGroup组件去掉,替换成Alert组件。注意Hello World是作为子项传递。保存后页面就显示Hello World了。

import Alert from "./components/Alert";
function App() {
  return (
    <div>
      <Alert>Hello World</Alert>
    </div>
  );
}
export default App;

如果我们希望在子项中传递带有html标记的内容,如

<Alert>Hello <span>World</span></Alert>

我们就要修改children的类型为ReactNode.

interface Props {
  children: ReactNode;
}

使用React开发工具检查组件


谷歌浏览器、微软EDGE、Firefox均有React开发工具插件(React Developer Tools),对于分析检查组件比较有用。

安装之后在开发者工具里就增加了Componets标签,点击可以查看网页组件的构成,查找具体的组件、查看源码等。很多功能都一目了然,这里不详述。

 


 

练习:创建一个按钮组件


查看getbootstrap网站的Button样式,我们将它封装成可重用的按钮组件。

 

直接给出结果,Button.tsx:

interface Props {
  children: string;
  onClick: () => void;
  color: string;
}

const Button = ({ children, onClick, color }: Props) => {
  return (
    <button className={"btn btn-" + color} onClick={onClick}>
      {children}
    </button>
  );
};

export default Button;

App.tsx:

import Button from "./components/Button";
function App() {
  return (
    <div>
      <Button color="success" onClick={() => console.log("clicked")}>
        My Button
      </Button>
    </div>
  );
}

export default App;

有些小问题。

要是App组件调用Button时不提供 color属性怎么办?我们应该有个默认值,免得报错。另外,如果提供的color是无效的话将会使页面显示不正常,也要限制。

在Props属性 color后加问号?,表示是可选的,我们也限制color的值只允许primary,danger,secondary中的一个。同时我们给color也提供了一个默认值:color = 'primary' 

interface Props {
  ...
  color?: "primary" | "danger" | "secondary";
}

const Button = ({ children, onClick, color = "primary" }: Props) => {
  ...
};

练习:显示警告栏


继续做练习。

当我们按下按钮时,我们希望顶部显示一个警告栏,该警告栏右上角有个关闭小按钮允许手动关闭。

警告栏关闭小按钮可以查询bootstrap的Alert的Dismissing部分,有相应的class。

 

我们在App组件中增加一个State,用来指示是否显示警告栏。还有当我们点击警告栏的关闭按钮,也需要将该事件通知给App组件处理。

Alert.tsx:

interface Props {
  children: string;
  onClose: () => void;
}

const Alert = ({ children, onClose }: Props) => {
  return (
    <div className="alert alert-warning alert-dismissible">
      {children}
      <button
        type="button"
        className="btn-close"
        data-bs-dismiss="alert"
        aria-label="Close"
        onClick={onClose}
      ></button>
    </div>
  );
};

export default Alert;

App.tsx:

import { useState } from "react";
import Alert from "./components/Alert";
import Button from "./components/Button";
function App() {
  const   [alertVisible, setAlertVisibility] = useState(false);
  return (
    <div>
      {alertVisible && (
        <Alert onClose={() => setAlertVisibility(false)}>My Alert</Alert>
      )}
      <Button onClick={() => setAlertVisibility(true)}>My Button</Button>
    </div>
  );
}

export default App;

效果如下:

小结


本篇介绍了React中创建组件的相关知识,我们了解了如何管理组件的状态以及如何传递数据和事件处理函数给组件。

 

下一篇计划介绍组件的样式。

最后于 2023-5-13 被admin编辑 ,原因:
可爱猫?Telegram电报群 https://t.me/ikeaimao

社区声明 1、本站提供的一切软件、教程和内容信息仅限用于学习和研究目的
2、本站资源为用户分享,如有侵权请邮件与我们联系处理敬请谅解!
3、本站信息来自网络,版权争议与本站无关。您必须在下载后的24小时之内,从您的电脑或手机中彻底删除上述内容
最新回复 (0)

您可以在 登录 or 注册 后,对此帖发表评论!

返回