【2023秋第11节课】初识React

375 阅读11分钟

QQ图片20231028231625 (2).png

本文编写自26届同学:狗儿要听狗儿歌

初识React

什么是React

React是一个用于构建用户界面的JavaScript库,由Facebook开发和维护。它允许开发者通过组合简单的组件来构建复杂的用户界面。

为什么选择React

  • 组件化:React的设计思想是将UI分解成独立的、可复用的组件。
  • 高效:React使用虚拟DOM来减少直接操作DOM的次数,从而提高性能。
  • 社区支持:React有一个庞大的开发者社区和丰富的生态系统。

创建一个React 项目

我们可以利用vite 来创建一个react项目(Vite 需要 Node.js 版本 18+,20+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。)

npm create vite@latest

image.png

经过漫长的等待和一系列选择后,一个react项目就创建好了,不过最好在桌面开启终端(右键桌面,点击打开终端),这样项目的文件会直接出现在桌面上,免得找不到

用你的编译器打开项目,并且在终端执行命令安装依赖,再用npm run dev 来启动项目

npm intall
npm run dev

image.png

启动成功可以看到一个链接,用浏览器打开就可以看到这个页面就表示这个项目没有问题

image.png

(我这深色模式看起来有点诡异)

那么就可以开始正式学习react了

React的核心概念

创建和嵌套组件

React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。

React 组件是返回标签的 JavaScript 函数:

function MyButton() {  
return (  
<button>I'm a button</button>  
);  
}

新建一个文件夹page,再新建button.tsx,同时我们需要删除掉一些没用的文件,将app.tsx return 里面删下面这样,将一些没用的css 文件也删掉,如下图,因为还有一些import 引用这些css , 所以连带着把所有引用css的地方也删干净,比如main.ts 中, 和app.tsx 中的一些import

image.png image.png

image.png

至此,你已经声明了 MyButton,现在把它嵌套到另一个组件中:

image.png

你可能已经注意到 <MyButton /> 是以大写字母开头的。你可以据此识别 React 组件。React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母。

如果你的项目还没有停止dev运行,再返回http://localhost:5173/ 就可以看到页面已经发生了变化,如果产生了报错,打开控制台,寻找报错原因

上面所使用的标签语法被称为 JSX。它是可选的,但大多数 React 项目会使用 JSX,主要是它很方便。所有 我们推荐的本地开发工具 都开箱即用地支持 JSX。

JSX 比 HTML 更加严格。你必须闭合标签,如 <br />。你的组件也不能返回多个 JSX 标签。你必须将它们包裹到一个共享的父级中,比如 <div>...</div> 或使用空的 <>...</> 包裹:

function AboutPage() {  
return (  
<>  
<h1>About</h1>  
<p>Hello there.<br />How do you do?</p>  
</>  
);  
}

不过因为我们选择的是typescript,所以相应的是tsx,其实只是语言变了,其他区别不大

export default function AboutPage() {
    const name = "张三"
    return (
        <>
            <h1 className="name" style={{color: "red"}}>{name}</h1>
            <p>Hello there.<br />How do you do?</p>
        </>
    );
}

在 React 中,你可以使用 className 来指定一个 CSS 的 class。它与 HTML 的 class 属性的工作方式相同:

然后,你可以在一个单独的 CSS 文件中为它编写 CSS 规则:

/* In your CSS */  
.name {  
border-radius: 50%;  
}

不过要想这段css 起作用,我们还需要将css文件用import 导入到对应的tsx 文件中

我们想要在jsx 中显示一个变量数据 如上面代码中的name 变量表示为 {name} 同时对于标签上的各种属性也可以使用{},再使用变量进行操作,{} 可以写表达式,但是不能写语句

style={{}} 并不是一个特殊的语法,而是 style={ } JSX 大括号内的一个普通 {} 对象。当你的样式依赖于 JavaScript 变量时,你可以使用 style 属性。)

return (  
<img  
className="avatar"  
src={user.imageUrl}  
/>  
);

条件渲染

当在遇到不同的条件下要渲染不同的组件时


let content;
if (isLoggedIn) {
   content = <AdminPanel />;
} else {
   content = <LoginForm />;
}
return (
   <div>
       {content}
   </div>
);

或者写为

<div>  
{isLoggedIn ? (  
<AdminPanel />  
) : (  
<LoginForm />  
)}  
</div>

这是三元表达式的写法,看不懂的话搜索三元表达式

当我们只需要一种情况时

<div>  
{isLoggedIn && <AdminPanel />} 
</div>

渲染列表

依赖 JavaScript 的特性,例如 for 循环array 的 map() 函数 来渲染组件列表。

const products = [
    { title: 'Cabbage', id: 1 },
    { title: 'Garlic', id: 2 },
    { title: 'Apple', id: 3 },
];

return (
    <>
        {products.map(({title,id},index) => {
            return <div key={index}>title: {title},id: {id}</div>
        })}
    </>
)

我们利用了map这个方法(不懂请查阅),map会遍历原数组并且返回一个新的数组,react 会将这个数组里的结构渲染,效果如下

image.png

事件响应

function handleClick() {
    alert('You clicked me!');
}

return (
    <button onClick={handleClick}>
        Click me
    </button>
);

和原生并不相同,在react 中我们应该尽量避免自己去调用 document.querySelector() 之类的api 去获取dom元素

更新界面

在原生的技术当中,如果我们要更改一个div中的文字,我们需要先获取这个div ,然后通过修改一些属性值的方式来修改这些文字,或者修改这个div的样式(颜色或字体大小.....),但在react中这件事变得非常简单

import {useState} from "react";

export default function MyButton() {

        const [count, setCount] = useState(0);

        function handleClick() {
            setCount(count + 1);
            console.log(count);
        }

        return (
            <button onClick={handleClick}>
                Clicked {count} times
            </button>
        );
}

20240318170043_rec_.gif

我们引入一个hook (以 use 开头的函数被称为 HookuseState 是 React 提供的一个内置 Hook。),通过这个hook,即可达到改变视图的作用,用法如上

这个hook 将返回一个长度为2 的数组,我们用两个变量来接住他们,其中 count 中存储的是一个number,而setCount 中存储的是一个函数,在调用这个函数的时候,组件会重新渲染一份新的,并且新的组件中count 的值 ,就是setCount 传入的值。也就是说,当我们调用setCount(2) 时,会产生一个新的快照,在那个快照下,count 的值为2。但是在这个快照下,count的值依旧为1,你可以理解为出现了两个MyButton组件.

通过控制台我们可以发现这么一个现象

20240318170836_rec_.gif

通过上面的思路我们即可理解这件事,当前快照下的count 并未发生改变

不过这个setCount() 不能直接裸写在 组件里像下面这样

image.png

如果你再去查看控制台会发现一堆报错,因为代码陷入了一种死循环当中,加载组件时,执行setCount(),再去加载新的快照,然后又遇到setCount() 这样就出现了死循环

组件间共享数据

对于两个兄弟组件,他们内部的变量是隔离开的,要想共享一份数据,就得在父组件中定义变量

function App() {
    const [count, setCount] = useState(0);

  return (
    <>
      <MyButton count={count} setCount={setCount}></MyButton>
      <MyButton count={count} setCount={setCount}></MyButton>
    </>
  )
}
interface Props {
    count: number,
    setCount: (count: number) => void
} // 相信你们能够看懂ts
export default function MyButton(props: Props) {
        const {count,setCount} = props;

        function handleClick() {
            setCount(count + 1);
            console.log(count);
        }

        return (
            <button onClick={handleClick}>
                Clicked {count} times
            </button>
        );
}

这里的props 就是接收父组件传过来的东西,你需要事先在子组件内定义propS的类型(ts)

20240318172138_rec_.gif

这样就实现了兄弟组件之间的数据共享

# 更新 state 中的对象和对象

 const [position, setPosition] = useState({
   x: 0,
   y: 0
 });

我们都知道,对于对象和数组来说,变量中存储的是这个对象或者数组的引用,当我们进行更改时需要特别注意,像下面这种写法就不能起到更新视图的效果

const [position, setPosition] = useState({
    x: 0,
    y: 0
});



return (
    <>
        <div style={{cursor: "pointer"}} onClick={() => {
            position.x = 100
            position.y = 100
            setPosition(position)
            console.log(position)
        }}>x:{position.x},y: {position.y}</div>
    </>
)

20240318173437_rec_.gif

可以发现除了最初加载此组件时触发的123之外setPosition()的调用显然没有起到重新加载组件的作用,因为react认为这个引用值没有发生改变,所以不需要重新加载,因此在这种情况下我们要换为下面这种写法


setPosition({
    ...position,
    x: 100
}) //将x 变为100 ,y值不变

这种写法在只改变对象中的一个属性值时特别有用

同理在数组中相信你也能够理解 image.png

使用 ref 引用值

当你希望组件“记住”某些信息,但又不想让这些信息 触发新的渲染 时,你可以使用 ref

观察下面这些代码

import {useRef, useState} from 'react';

export default function Counter() {
    const ref = useRef(0);
    let times = 0;
    const [f5,setF5] = useState(0);

    function handleClick() {
        ref.current = ref.current + 1;
        times ++ ;
        console.log('你点击了 ' + ref.current + ' 次!');
        console.log(times);
        setF5(f5 + 1);
    }

    return (
        <button onClick={handleClick}>
            点击我!
        </button>
    );
}

此处的f5 只是起到刷新此组件的作用

20240318174802_rec_.gif

你会发现通过useRef()保管的值是不会因为刷新而重新变回初始值的,而普通的变量会因为组件的刷新,多次赋值为0,useRef 和 useState 的区别仅仅是,useRef 不会返回状态更新函数。

# 使用 ref 操作 DOM

由于 React 会自动处理更新 DOM 以匹配你的渲染输出,因此你在组件中通常不需要操作 DOM。但是,有时你可能需要访问由 React 管理的 DOM 元素 —— 例如,让一个节点获得焦点、滚动到它或测量它的尺寸和位置。在 React 中没有内置的方法来做这些事情,所以你需要一个指向 DOM 节点的 ref 来实现。


const myRef = useRef(null);

return (
<>
 <div ref={myRef}>
</>
)

// 你可以使用任意浏览器 API,例如:  
myRef.current.scrollIntoView();

不过在使用之前你最好先判断myRef.current 是否为null或者undefinde,你可以理解为获取dom 需要一定的时间( useRef Hook 返回一个对象,该对象有一个名为 current 的属性。最初,myRef.currentnull。当 React 为这个 <div> 创建一个 DOM 节点时,React 会把对该节点的引用放入 myRef.current。然后,你可以从 事件处理器 访问此 DOM 节点,并使用在其上定义的内置浏览器 API。)

# 使用 Effect 同步

有些组件需要与外部系统同步。例如,你可能希望根据 React state 控制非 React 组件、设置服务器连接或在组件出现在屏幕上时发送分析日志。Effects 会在渲染后运行一些代码,以便可以将组件与 React 之外的某些系统同步。

不要随意在你的组件中使用 Effect。记住,Effect 通常用于暂时“跳出” React 代码并与一些 外部 系统进行同步。这包括浏览器 API、第三方小部件,以及网络等等。如果你想用 Effect 仅根据其他状态调整某些状态,那么 你可能不需要 Effect

编写 Effect 需要遵循以下三个规则:

  1. 声明 Effect。默认情况下,Effect 会在每次渲染后都会执行。
  2. 指定 Effect 依赖。大多数 Effect 应该按需执行,而不是在每次渲染后都执行。例如,淡入动画应该只在组件出现时触发。连接和断开服务器的操作只应在组件出现和消失时,或者切换聊天室时执行。文章将介绍如何通过指定依赖来控制如何按需执行。
  3. 必要时添加清理(cleanup)函数。有时 Effect 需要指定如何停止、撤销,或者清除它的效果。例如,“连接”操作需要“断连”,“订阅”需要“退订”,“获取”既需要“取消”也需要“忽略”。你将学习如何使用 清理函数 来做到这一切。
function MyComponent() {  
useEffect(() => {  
// 每次渲染后都会执行此处的代码  
});  
return <div />;  
}

每当你的组件渲染时,React 将更新屏幕,然后运行 useEffect 中的代码。换句话说,useEffect 会把这段代码放到屏幕更新渲染之后执行

useEffect(() => {  
// 这里的代码会在每次渲染后执行  
});
useEffect(() => {  
// 这里的代码只会在组件挂载后执行(且只执行一次(或者说两次))
}, []);  
useEffect(() => {  
//这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行  
}, [a, b]);

后面的这个数组是一个可选项

没有依赖数组作为第二个参数,与依赖数组位空数组 [] 的行为是不一致的:

  • 与事件不同,Effect 是由渲染本身,而非特定交互引起的。
  • Effect 允许你将组件与某些外部系统(第三方 API、网络等)同步。
  • 默认情况下,Effect 在每次渲染(包括初始渲染)后运行。
  • 如果 React 的所有依赖项都与上次渲染时的值相同,则将跳过本次 Effect。
  • 不能随意选择依赖项,它们是由 Effect 内部的代码决定的。
  • 空的依赖数组([])对应于组件“挂载”,即添加到屏幕上。
  • 仅在严格模式下的开发环境中,React 会挂载两次组件,以对 Effect 进行压力测试。
  • 如果 Effect 因为重新挂载而中断,那么需要实现一个清理函数。
  • React 将在下次 Effect 运行之前以及卸载期间这两个时候调用清理函数。

useEffect 的内容很多也很关键,建议大家去翻翻官网再仔细看看,我写不完了

作业:没有作业,react这部分要学的很多,这课件实在是写不完,大家加油