React18学习笔记

146 阅读10分钟

创建项目

npx create-react-app project-name

基本语法

列表循环

function ListFor() {
  const list = [
    { id: 1, name: "AAA" },
    { id: 2, name: "BBB" },
    { id: 3, name: "CCC" },
    { id: 4, name: "DDD" },
  ];

  return (
    <ul>
      {list.map((item) => {
        return <li key={item.id}>{item.name}</li>;
      })}
    </ul>
  );
}

export default ListFor;

条件渲染

function IfView() {
  const flag = true;

  let content = null;
  if (flag) {
    content = <h1>Flag is true</h1>;
  } else {
    content = <h1>Flag is false</h1>;
  }

  return (
    <>
      {/* 第一种 三元 */}
      {/* {flag ? <h1>Flag is true</h1> : <h1>Flag is false</h1>} */}
      {/* 第二种 逻辑与 */}
      {/* {flag && <h1>Flag is true</h1>} */}
      {/* 第三种 if else */}
      {content}
    </>
  );
}

export default IfView;

react基础事件绑定

function ClickBind() {
  // 获取事件对象e
  const handleClick = (e) => {
    console.log("Button clicked");
    console.log("获取事件对象参数", e);
  };

  // 传递参数
  const handleParamClick = (param) => {
    console.log("Button clicked with param", param);
  };

  // 同时传递事件对象和自定义参数
  const handleEventAndParamClick = (param, e) => {
    console.log("Button clicked with event and param", param, e);
  };

  const aaa = 100;
  return (
    <div>
      <h1>ClickBind</h1>
      <button onClick={handleClick}>Click me</button>
      {/* 传递自定义参数 */}
      {/* 注意:不能直接写函数调用,这里的事件绑定需要一个函数引用 */}
      <button onClick={() => handleParamClick(aaa)}>传递自定义参数</button>
      {/* 同时传递事件对象和自定义参数 */}
      <button onClick={(e) => handleEventAndParamClick(aaa, e)}>
        同时传递事件对象和自定义参数
      </button>
    </div>
  );
}

export default ClickBind;

React组件

概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次。

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和试图UI,渲染组件只需要把组件当成标签书写即可。

function Button(){
  return <button>Click me! </button>
}

function App () {
  return (
    <div className="App">
      <Button />
      <Button></Button>
    </div>
  )
}

React 中的 useState

概念:useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量。从而控制影响组件的渲染结果。

本质:和普通JS变量不同的是,状态变量一旦发生变化,组件的视图UI也会跟着变化(数据驱动视图)

import { useState } from "react";

function TryUseState() {
  // 调用 useState 添加一个状态变量
  //   count 是状态变量的名称,setCount 是更新状态变量的函数
  const [count, setCount] = useState(0);
  const [name, setName] = useState("John");

  const increment = () => {
    // 用传入的新值修改 count
    setCount(count + 1);
  };

  return (
    <div>
      <h1>Try Use State</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <p>Name: {name}</p>
    </div>
  );
}

export default TryUseState;

修改状态的规则

在 React 中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接i需改状态不能引发视图更新

import { useState } from "react";

function TryUseState() {
  // 调用 useState 添加一个状态变量
  //   count 是状态变量的名称,setCount 是更新状态变量的函数
  let [count, setCount] = useState(0);
  const [name, setName] = useState("John");

  const increment = () => {
    // 直接修改,无法引起视图更新
    count++;
    console.log(count); // 打印出来是修改的

    // 用传入的新值修改 count
    // setCount(count + 1);
  };

  return (
    <div>
      <h1>Try Use State</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <p>Name: {name}</p>
    </div>
  );
}

export default TryUseState;

修改对象状态

import { useState } from "react";

function TryUseState() {
  const [form, setForm] = useState({
    name: "John",
    age: 20,
  });

  const handleChange = () => {
    setForm({
      ...form,
      name: "Bob",
      age: 30,
    });
  };

  return (
    <div>
      <h1>Try Use State</h1>
      <p>Name: {form.name}</p>
      <p>Age: {form.age}</p>
      <button onClick={handleChange}>ChangeForm</button>
    </div>
  );
}

export default TryUseState;

数组的修改

在 React 中你需要将数组视为只读的,不可以直接修改原数组。例如,不可以调用 arr.push()arr.pop()等方法。也推荐使用 lodash库的方法

数组新增数据

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr([...arr,4]) //末尾新增 扩展运算符
    //setArr([0,...arr]) 头部新增 扩展运算符
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

数组删除数据

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr(arr.filter((item) => item !== 1)) //删除指定元素
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

数组替换数据

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    setArr(arr.map(item => {
      return item == 2 ? 666 : item
    }))
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

指定位置插入元素

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    let startIndex = 0
    let endIndex = 2;
    setArr(
      [
        ...arr.slice(startIndex, endIndex),
        2.5,
        ...arr.slice(endIndex)
      ]
    )
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

数组的排序旋转等

import { useState } from "react"
function App() {
  let [arr, setArr] = useState([1, 2, 3])
  const heandleClick = () => {
    let newList = [...arr].map(v => v + 1) //拷贝到新数组
    newList.sort((a, b) => b - a)
    //newList.reverse()旋转
    setArr(newList)
  }
  return (
    <>
      <button onClick={heandleClick}>更改值</button>
      <div id="aaa">{arr}</div>
    </>
  )
}
export default App

组件样式处理

行内样式(不推荐)

function StyleClass() {
  return (
    <div
      style={{
        backgroundColor: "red",
        width: "100px",
        height: "100px",
      }}
    ></div>
  );
}

export default StyleClass;

class类名控制

.box{
    width: 100px;
    height: 100px;
    background-color: red;
}
// 导入样式文件
import "./index.css";

function StyleClass() {
  return (
    // <div
    //   style={{
    //     backgroundColor: "red",
    //     width: "100px",
    //     height: "100px",
    //   }}
    // ></div>
    <div className="box">Hello</div>
  );
}

export default StyleClass;

动态样式绑定

import { useState } from "react";
import "./index.css";
function StyleClass() {
  const [isRed, setIsRed] = useState(false);

  const changeColor = () => {
    setIsRed(!isRed);
  };

  return (
    <>
      <div>
        <div className={`box ${isRed ? "box-active" : ""}`}>Hello</div>
        <button onClick={changeColor}>切换样式</button>
      </div>
    </>
  );
}

export default StyleClass;

classnames优化类名控制

classnames 是一个简单的 JS 库,可以非常方便的通过条件动态控制 class类名的显示

安装

npm i classnames

使用

import { useState } from "react";
import "./index.css";
import classNames from "classnames";

function StyleClass() {
  const [isRed, setIsRed] = useState(false);

  const changeColor = () => {
    setIsRed(!isRed);
  };

  return (
    <>
      <div>
        {/* <div className={`box ${isRed ? "box-active" : ""}`}>Hello</div> */}
        <div className={classNames("box", { "box-active": isRed })}>Hello</div>
        <button onClick={changeColor}>切换样式</button>
      </div>
    </>
  );
}

export default StyleClass;

受控表单绑定

概念:使用 React 组件的状态(useState)控制表单的状态

import { useState } from "react";

function Form() {
  const [inputValue, setInputValue] = useState("");

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
    console.log("当前输入框的值", inputValue);
  };
  return (
    <>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => handleInputChange(e)}
      />
      <div>
        <button>按钮</button>
      </div>
    </>
  );
}

export default Form;

React 中的 useRef

在 React 中获取/操作 DOM,需要使用 useRef钩子函数,分为两步:

  1. 使用useRef 创建 ref 对象,并与 JSX 绑定
const inputRef = useRef(null)

<input type="text" ref={inputRef} />
  1. 在 DOM 可用时,通过 inputRef.current 拿到 DOM 对象
import { useRef } from "react";

function UseRef() {
  const inputRef = useRef(null);

  // dom 可用时,通过 ref.current 获取 input 元素
  const showDom = () => {
    console.log(inputRef.current);
  };

  return (
    <>
      <input type="text" ref={inputRef} />
      <div>
        <button onClick={showDom}>获取DOM</button>
      </div>
    </>
  );
}

export default UseRef;

组件通信

概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信方法

父子通信

父传子

注意:子组件只能读取 props中的数据,不能直接进行修改,父组件的数据只能由父组件修改。

import { useState } from "react";
import ChildOne from "./ChildOne";
import "./parent.css";

export default function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <div className="container">
        <h1>我是父组件</h1>
        <button onClick={() => setCount(count + 1)}>count增加1</button>
      </div>
      <hr />
      <ChildOne count={count} />
    </>
  );
}
function ChildOne({ count }) {
  return (
    <div style={{ padding: "10px", backgroundColor: "#eee" }}>
      <h1>我是ChileOne组件</h1>
      <hr />
      <h1>父组件传递过来的count值:{count}</h1>
    </div>
  );
}

export default ChildOne;

特殊的 prop children

场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为 children的 prop 属性中接收该内容。

<Son>
  <span>This is a span</span>
</Son>

props:
  children:<span />

子传父

核心:在子组件中调用父组件中的函数并传递实参

import { useState } from "react";
import ChildOne from "./ChildOne";
import "./parent.css";

export default function Parent() {
  const [count, setCount] = useState(0);

  const receiveChild = (data) => {
    console.log("父组件接收到的数据", data);
  };

  return (
    <>
      <div className="container">
        <h1>我是父组件</h1>
        <button onClick={() => setCount(count + 1)}>count增加1</button>
      </div>
      <hr />
      <ChildOne count={count} onReceive={receiveChild} />
    </>
  );
}
import { useState } from "react";

function ChildOne({ count, onReceive }) {
  const [name, setName] = useState("Child One");

  return (
    <div style={{ padding: "10px", backgroundColor: "#eee" }}>
      <h1>我是ChileOne组件</h1>
      <hr />
      <h1>父组件传递过来的count值:{count}</h1>
      <button onClick={() => onReceive(name)}>向父组件传递数据</button>
    </div>
  );
}

export default ChildOne;

兄弟通信

使用状态提升实现兄弟组件通信

实现思路:借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递

import { useState } from "react";
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
import "./parent.css";

export default function Parent() {
  const [count, setCount] = useState(0);

  const receiveChild = (num) => {
    console.log("父组件接收到的数据", num);
    setCount(num);
  };

  return (
    <>
      <div className="container">
        <h1>我是父组件</h1>
        <p>ChildOne传递的num值:{count}</p>
      </div>
      <hr />
      <ChildOne onReceive={receiveChild} />
      <hr />
      <ChildTwo count={count} />
    </>
  );
}
import { useState } from "react";

function ChildOne({ onReceive }) {
  const [num, setNum] = useState(100);

  const sendNum = () => {
    setNum(num + 1);
    onReceive(num);
  };

  return (
    <div style={{ padding: "10px", backgroundColor: "#eee" }}>
      <h1>我是ChileOne组件</h1>
      <hr />
      <button onClick={() => sendNum()}>向父组件传递数据</button>
    </div>
  );
}

export default ChildOne;
function ChildTwo({ count }) {
  return (
    <div style={{ backgroundColor: "pink", padding: "10px" }}>
      <h1>我是Child Two组件</h1>
      <p>This is the second child component</p>
      <p>接收到的从 ChildOne 组件传递的 Count: {count}</p>
    </div>
  );
}

export default ChildTwo;

跨层通信

使用 Context机制跨层级组件通信

实现步骤:

使用 CreateContext方法创建一个上下文对象 Ctx

在顶层组件中(Top)通过 Ctx.Provider 组件提供数据

在底层组件(Bottom)中通过 useContext钩子函数获取消费数据

示例:跨文件使用 CreateContext

import { useState } from "react";
import Middle from "./Middle";

import { MyContext } from "./pass";

function Top() {
  const [message, setMessage] = useState("Hello from Top");

  const handleClick = () => {
    setMessage("Top Message Changed");
  };

  return (
    <>
      <MyContext.Provider value={message}>
        This is Top component.
        <hr />
        <button onClick={() => handleClick()}>修改数据观察是否传递</button>
        <hr />
        <Middle />
      </MyContext.Provider>
    </>
  );
}

export default Top;
import Bottom from "./Bottom";

function Middle() {
  return (
    <div style={{ border: "1px solid black" }}>
      <h1> This is Middle component.</h1>
      <hr />
      <Bottom />
    </div>
  );
}

export default Middle;
import { useContext } from "react";
import { MyContext } from "./pass";

function Bottom() {
  const message = useContext(MyContext);

  return (
    <>
      <h1>我是底层组件</h1>
      <hr />
      <h1>接收到的数据是:{message}</h1>
    </>
  );
}

export default Bottom;

定义的中间文件

import { createContext } from "react";

// 1. 使用 CreateContext方法创建一个上下文对象 Ctx
const MyContext = createContext();

// 将其导出供顶层组件及底层组件使用
export { MyContext };

React 中的 useEffect

useEffect是一个 React Hook 函数,用于在 React 组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送 AJAX 请求,更改 DOM 等。

基本使用

语法

useEffect(() => { },[])

参数1是一个函数,可以将其称为副作用函数,在函数内部可以放置需要执行的操作

参数2是一个数组(可选参数),在数组里放置依赖项,不同的依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

需求:在组件渲染完毕之后,立刻从服务端获取到列表数据并显示在页面中

import { useEffect, useState } from "react";

const URL = "http://geek.itheima.net/v1_0/channels";

export default function TryUseEffect() {
  const [channels, setChannels] = useState([]);
  //   const [loading, setLoading] = useState(true);

  useEffect(() => {
    const getChannels = async () => {
      const res = await fetch(URL);
      const list = await res.json();
      console.log(222, list.data.channels);
      setChannels(list.data.channels);
    };
    getChannels();
  }, []);

  return (
    <>
      <h1>This is TryUseEffect Component!</h1>
      <div>下面是频道列表</div>
      <hr />
      <ul>
        {channels.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

useEffect依赖项参数说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现。

没有依赖项

在组件初始渲染和组件更新时执行

import { useEffect, useState } from "react";

export default function TryUseEffect() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect 执行啦!");
  });

  return (
    <>
      <h1>This is TryUseEffect Component! {count} </h1>
      <button onClick={() => setCount(count + 1)}>修改 count 的值</button>
    </>
  );
}

// 执行情况
// 组件渲染时执行了一次,每次修改 count 的值,副作用函数也都会执行

空数组依赖项

只在初始渲染时执行一次

import { useEffect, useState } from "react";

export default function TryUseEffect() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("useEffect 执行啦!");
  }, []);

  return (
    <>
      <h1>This is TryUseEffect Component! {count} </h1>
      <button onClick={() => setCount(count + 1)}>修改 count 的值</button>
    </>
  );
}

// 执行情况
// 只在组件初始渲染时执行了一次,后续 count 值变化时副作用函数也没有执行

特定依赖项

import { useEffect, useState } from "react";

export default function TryUseEffect() {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(2);

  useEffect(() => {
    console.log("useEffect 执行啦!");
  }, [count]);

  return (
    <>
      <h1>
        This is TryUseEffect Component! Count: {count} Num: {num}
      </h1>
      <button onClick={() => setCount(count + 1)}>修改 count 的值</button>
      <hr />
      <button onClick={() => setNum(num * 2)}>修改 num 的值</button>
    </>
  );
}

// 执行情况
// 组件初始渲染时执行了一次,依赖项 count 变化时,副作用函数也会执行,而 num 变化时,副作用
// 函数并未执行

useEffect清除副作用

useEffect中编写由渲染本身引起的对接组件外部的操作,称作副作用操作,比如在 useEffect中开启了一个定时器,我们想在组件卸载时把定时器清理掉,这个过程就是清理副作用。

useEffect(() => {
  // 实现副作用操作逻辑
  
  return ()=>{
    // 清除副作用逻辑
    
  }
},[])

// 清除副作用的函数最常见的执行时机时在组件卸载时自动执行

实例

import { useState, useEffect } from "react";

function Son() {
  // 渲染完毕后开启定时器,组件销毁时清除定时器
  useEffect(() => {
    const timer = setInterval(() => {
      console.log("定时器开启");
    }, 1000);
    return () => {
      clearInterval(timer);
      console.log("定时器关闭");
    };
  });
  return (
    <div style={{ backgroundColor: "red", width: "100%", height: "200px" }}>
      我是子组件
      <hr />
    </div>
  );
}

function Father() {
  const [flag, setFlag] = useState(true);
  return (
    <>
      我是父组件
      <p></p>
      <button onClick={() => setFlag(!flag)}>切换</button>
      <hr />
      <p>下面是子组件</p>
      {flag && <Son />}
    </>
  );
}

export default Father;

自定义Hook函数

基础使用

例:切换组件的显示与隐藏效果

import { useState } from "react";

function Custom() {
  const [show, setShow] = useState(false);

  return (
    <div>
      <button onClick={() => setShow(!show)}>Toggle</button>
      {show && <p>This is a custom hook test</p>}
    </div>
  );
}

export default Custom;

// 问题:布尔值且切换部分的逻辑,当要使用切换的场景过多时,需要复制多份,不方便服用,当前组件耦合
//  		 度太高,这就需要自定义一个 Hook 方便复用

抽离切换逻辑,自定义Hook函数使用

import { useState } from "react";

export default function useToggle(initialState = false) {
  // 创建一个状态变量和一个更新状态的函数
  const [state, setState] = useState(initialState);

  // 创建一个切换状态的函数
  const toggle = () => setState(!state);

  // 返回状态变量和切换状态的函数
  return {
    state,
    toggle,
  };
}
import useToggle from "./useToggle";

function Custom() {
  const { state, toggle } = useToggle();

  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      {state && <p>This is a custom hook test</p>}
    </div>
  );
}

export default Custom;

React Hooks使用规则

  1. 只能在组件中或者其他自定义 Hook 函数中调用
  2. 只能在组件的顶层调用,不能嵌套在 if、for 等其他函数中