卡壳了再看看呢?

11 阅读6分钟

React基础(二)

上一篇已经知道了怎么搭建一个新的项目了,那么这时候,我们看看怎么编写页面的内容,丰富页面,并且便于后期管理。

首先需要了解到框架中的组件化思维。按照目前我们创建的项目,是无法做到从全局范围收缩到单个小组件进行操作的,这样就会导致随着项目越来越大,调整起来会非常繁琐。

组件化

组件 = 关注点最小闭环 :一个组件只负责一件事(展示、交互、数据加载中的某一类),边界清晰,便于测试与复用。

  • Header.tsx
export const Header = () => {
    return (
        <div>
            Header
        </div>
    )
}
  • main.tsx(父组件)
import { createRoot } from "react-dom/client";
import { Header } from "./components/Header";
import "./main.css";
const app = createRoot(document.getElementById("app")!);

app.render(
  <div>
    <Header></Header>
  </div>
);

引入组件就可以时候组件标签了,这时候会出现一个ts编译的问题:

image.png

这时候似乎意识到,我创建的是前端项目,而不是node项目,这是需要修改一下tsconfig.json文件:

{
   "module": "ESNext",
   "moduleResolution": "bundler",
}

现在就能够引入组件了,最小单元化的项目编写开始了~

CSS Module

这时候想要改组件的样式,需要怎么做呢?有很多种方式,包括但不限于:内联样式​、CSS Module​、Styled Components​、Emotion​、Tailwind CSS

这里我们使用CSS Module的方式进行。

我们需要把命名写成*.module.css​的方式,这是vite​的默认方式,这么写就会把文件当作CSS Module 处理。

这里涉及到的类名需要用className​而不是class​。这是因为class是关键字,在js中是类的定义,jsx的编译过程也是js化的过程

  • index.tsx

    import styles from "./index.module.css";
    export const Header = () => {
      return <div className={styles.container}>Header</div>;
    };
    

  • index.module.css

    .container {
        width: 100vw;
        height: 48px;
        background: #aaa;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    

最后我们的文件结构就是:

image.png

这里开始,就已经可以制作想要的任何页面了!!

JSX

我们开始频繁和jsx​打交道了。jsx​ 是React 项目中的模版,可以理解成 React 在看到 jsx​的这种写法的时候,会把他识别转化成 js​的方式创建DOM节点,事件等等。这个过程就涉及了React对于jsx​的处理方式,在现阶段不进行深入了解,大概知道其实最后还是会通过React 转化成 js 语言。因为其写法与传统的HTML非常一致,所以其实在使用层面十分友好,只是背后存在一系列的编译过程是React帮我们做好的了。

简单理解就是 jsx = js + HTML

需要记住的是:

{*}中写入表达式或变量

className表示的是类名

事件

到现在,我们基本上想要完成一个项目是没有问题的,但是中间还存在一些细节的问题。事件,类似点击事件、修改事件等,直接可以通过一个函数调用的方式触发。这里面的写法与常规JS的写法无异,直接按照js的写法写就行了。

import styles from "./index.module.css";
export const Header = () => {
  const handleClick = () => {
    console.log("点击了Header");
  };
  return (
    <div className={styles.container} onClick={handleClick}>
      Header
    </div>
  );
};

内部状态管理

好了,现在我有一个需求:通过加减号来使得页面的内容跟着加减按钮事件变化而变化。 你会怎么做呢?

当还未了解更多的知识,我会这么写:

import { useCallback } from "react";
import styles from "./index.module.css";
export const Header = () => {
  let number = 0;
  const handleAdd = () => {
    number += 1;
  };
  const handleSub = () => {
    number -= 1;
  };

  return (
    <div className={styles.container}>
      Header
      <h1>{number}</h1>
      <button onClick={handleAdd}>+</button>
      <button onClick={handleSub}>-</button>
    </div>
  );
};

这么写问题就来了,你会发现怎么点击➕都是无效的。为什么呢?对啊,你没有重新修改DOM节点呢!那我们需要重新获取DOM节点再使用innerHtml​或 innerText进行修改吗?答案是:不需要!!

React 就是这样把 UI 和 数据 分开的。我们操纵数据就足够了,UI 会在React 的内部帮我们进行,也就是进行我们刚才说到的:重新获取DOM节点再使用innerHtml​或 innerText进行修改。也就是React帮我们省去了这部分的内容,这就是框架的威力!

那么,既然要省去这部分内容,需要做什么?

我们就需要把这个需要随时变化的数据告诉React,我们称这样的数据为 内部状态值 。 React中我们需要这样写:

import {  useState } from "react";
import styles from "./index.module.css";
export const Header = () => {
  const [number, setNumber] = useState(0);
  const handleAdd = () => {
    setNumber(number + 1);
  };
  const handleSub = () => {
    setNumber(number - 1);
  };

  return (
    <div className={styles.container}>
      Header
      <h1>{number}</h1>
      <button onClick={handleAdd}>+</button>
      <button onClick={handleSub}>-</button>
    </div>
  );
};

似乎变化没有那么大嘛,使用useState就能做到了。这也是一个hook。

这时候发现,需求完成了,成功改变了内容,而且不再需要操作DOM节点了。惊不惊喜意不意外。

传值

好了,上面的内容都是在单个组件内的一些细节。但是回到整个项目中,创建的组件肯定是非常多的,这时候就涉及到一个问题:组件之间的状态如何传递?

学习完状态值都知道通过数据控制页面内容的更新。这个数据肯定不会只是在单个组件中运用的,所以有时候需要让其流动在组件之间。

组件之间数值的传递可以有很多种方式,在后面会逐渐接收到很多的方式。我们先看看父子之间的传递,这种方式虽然只能是父子之间的传递,但是继续延生也是可以让数据在所有组件中传递的。

  • props传值

    从代码中看出方式:

    main.tsx(父组件)

    import { createRoot } from "react-dom/client";
    import { Header } from "./components/Header";
    
    import "./main.css";
    const app = createRoot(document.getElementById("app")!);
    
    app.render(
      <div>
        <Header title="Hello React"></Header>
      </div>
    );
    
    

    index.tsx(自组件)

    import { useState } from "react";
    import styles from "./index.module.css";
    interface HeaderProps {
      title: string;
    }
    export const Header = (props: HeaderProps) => {
      const [number, setNumber] = useState(0);
      const handleAdd = () => {
        setNumber(number + 1);
      };
      const handleSub = () => {
        setNumber(number - 1);
      };
    
      return (
        <div className={styles.container}>
          {props.title}
          <h1>{number}</h1>
          <button onClick={handleAdd}>+</button>
          <button onClick={handleSub}>-</button>
        </div>
      );
    };
    
    

这样就实现了父子之间的传值。简单的吧~

需要记住:props的传递是单向数据流,也就是子组件是无法直接修改父组件的值的。

副作用

这个功能是很神奇也是很强大的。这让状态更改的过程中存在某些额外作用的时候,都可以写在这里面。

import { useEffect, useState } from "react";
import styles from "./index.module.css";
interface HeaderProps {
  title: string;
  onChange: () => void;
}
export const Header = (props: HeaderProps) => {
  const [number, setNumber] = useState(0);
  const handleAdd = () => {
    setNumber(number + 1);
  };
  const handleSub = () => {
    setNumber(number - 1);
  };

  useEffect(() => {
    props.onChange();
  }, [props, number]);

  return (
    <div className={styles.container}>
      {props.title}
      <h1>{number}</h1>
      <button onClick={handleAdd}>+</button>
      <button onClick={handleSub}>-</button>
    </div>
  );
};

副作用只会在number​修改的时候才会进行,这时候需要进行的操作对于修改number来说就是副作用。

这里需要注意:

如果全局开启了严格模式

import { createRoot } from "react-dom/client";
import { Header } from "./components/Header";

import "./main.css";
import { StrictMode } from "react";
const app = createRoot(document.getElementById("app")!);

const handleChange = () => {
  console.log("更改了值!!!");
};

app.render(
  <StrictMode> // 严格模式
    <div>
      <Header title="Hello React" onChange={handleChange}></Header>
    </div>
  </StrictMode>
);

那么副作用会执行两次,这是在开发模式的时候会进行的,主要是用于进行组件的检查,会卸载再重新加载,所以会执行两次。

至此,了解这些都足以看懂和创建一些项目了~ 开始试着做一些项目吧,会发现越来越有趣的事情的~