30岁前端程序员的学习之路----React 18基础

184 阅读13分钟

30岁前端程序员的学习之路----React 18基础

hello,大家好,我是一名30岁程序员,目前在学react 基础,感兴趣的小伙伴,可以看一下。

React 18 基础

1. jsx 基础语法

const test = "学习jsx语法";
const fn = function() {
  return "实现函数调用";
};
const flag = false;
function App() {
  return (
    <div className="App">
      <p>{`基本使用:${test}`}</p>
      <p>函数调用:{fn()}</p>
      <p>使用js方法:{new Date().getDay()}</p>
      <p style={{ color: "red" }}>设置字体颜色</p>
      <p>{flag ? true : false}</p>
    </div>
  );
}

2.使用 map 渲染列表

let list = [
  {
    name: "Vue",
    id: "10001",
  },
  {
    name: "React",
    id: "10002",
  },
  {
    name: "Angule",
    id: "10003",
  },
];
function App() {
  return (
    <div>
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

3.条件渲染

let flag = 1;
let hanlderFlag = function(flag) {
  if (flag === 1) {
    return <div>多图</div>;
  } else if (flag === 2) {
    return <div>多图</div>;
  } else {
    return <div>无图</div>;
  }
};
function App() {
  return <div>{hanlderFlag(9)}</div>;
}

4.事件绑定

// 普通绑定事件;
let hanlderBtn = function() {
  console.log("普通绑定事件");
};
// 传递事件参数e
let handerEvent = function(e) {
  console.log(e);
};

// 传递参数
let hanlderName = function(e, name) {
  console.log(e, name);
};

function App() {
  return (
    <div>
      <button onClick={hanlderBtn}>普通绑定事件</button>
      <button onClick={(e) => handerEvent(e)}>传递事件参数</button>
      <button onClick={(e) => hanlderName(e, "小明")}>传递参数</button>
    </div>
  );
}

5.组件的基础使用

组件就是一个首字母大写的函数

function Button() {
  return <button onClick={() => hanlderClick()}>点击按钮组件</button>;
}
function hanlderClick() {
  console.log("点击按钮组件事件");
}

function App() {
  return (
    <div>
      {/* 组件的使用 */}
      <Button></Button>
    </div>
  );
}

6.useState 使用

  1. 使用 useState 可以声明一个状态变量,返回的是一个数组

  2. 用 useState 声明的变量,是数据影响视图的

  3. 数组的第一项是一个初始值

  4. 数组的第二项是改变初始值的方法

注意:修改对象规则,不能直接去修改数据,而是必须通过我们声明的修改的函数用一个全新的对象来进行修改

// 首先先从 react 导入 useState
import { useState } from "react";

function App() {
  // useState 中的值就是声明变量的初始值

  let [cont, setCont] = useState(0);

  // 声明一个对象
  let [form, setForm] = useState({
    name: "小明",
    age: 18,
  });
  // 声明一个函数,用来修改 cont 的值
  let hanlderAdd = (num) => {
    // 错误的写法,不能直接通过 cont+=num
    // cont+=num
    // 只能通过我们传入的新的对象去进行修改
    setCont((cont += num));
    console.log(cont);
  };
  let hanlderDel = (num) => {
    setCont((cont -= num));
    console.log(cont);
  };
  // 更改 form 的 name 属性
  let changeName = (name) => {
    // 必须要传入一个新的对象,而不能直接改变 form 身上的属性值
    setForm({
      ...form,
      name,
    });
    console.log(form);
  };
  return (
    <div>
      <p>{cont}</p>
      <button onClick={() => hanlderAdd(2)}>自增</button>
      <button onClick={() => hanlderDel(1)}>自减</button>
      <p>{form.name}</p>
      <button onClick={() => changeName("jack")}>更改 form 的 name 属性</button>
    </div>
  );
}
  1. useState 可以用函数的形式进赋值
    举例:用来获取当前日期,因为不能写死一个日期,所以用函数的形式,初始化默认值
function App() {
  const [date] = useState(() => {
    const cur = new Date();
    console.log(cur.getFullYear);
    return {
      year: cur.getFullYear(),
      month: cur.getMonth() + 1,
      day: cur.getDate(),
    };
  });
  return (
    <div>
      <p>年:{date.year}</p>
      <p>月:{date.month}</p>
      <p>日:{date.day}</p>
    </div>
  );
}
  1. useState是异步更新的
import { useState, useEffect } from "react";
function App() {
  const [count, setCount] = useState(0);
  const add = () => {
    setCount(count + 1);
    // console.log(count); // 异步更新的,所以这里拿不到最新的coun的值
  };
  // 我们可以借助useEffect,监听count的值
  useEffect(() => {
    console.log(count); // 可以拿到最新的值
  }, [count]);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => add()}>+1</button>
    </div>
  );
}
  1. useState 当我们修改state的时候,如果新值依赖旧值的时候(基于旧值得到新值的时候)
    此时不能直接进行计算,而需要使用fn函数形参拿到旧值,并进行计算
import { useState, useEffect } from "react";
function App() {
  const [count, setCount] = useState(0);
  const add = () => {
    // 问题:在这里连续调用两次setCount,并不会直接进行+2,而是还是1,因为他们内部设置是异步的
    // setCount(count + 1);
    // setCount(count + 1);

    // 解决:
    // 如果想得到的新值,是基于旧值进行自增的话,需要使用fn函数的形式,fn的形参就是上一次计算的值,然后再将上次计算的值+1
    // 结果:每次都会自增2
    setCount((pre) => pre + 1);
    setCount((pre) => pre + 1);
  };
  // 我们可以借助useEffect,监听count的值
  useEffect(() => {
    console.log(count); // 可以拿到最新的值
  }, [count]);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => add()}>+1</button>
    </div>
  );
}
  1. 使用 useState模拟强制刷新
  • useState 创建一个空对象,并且设置一个方法
  • 点击按钮刷新时,调用这个方法,传入一个新的空对象
import { useState } from "react";

function App() {
  // 定义一个forceUpdate函数用来更新状态
  const [, forceUpdate] = useState({});
  // 强制更新
  const update = () => forceUpdate({});
  return (
    <div>
      <p>{Date.now()}</p>
      <button onClick={update}>强制更新</button>
    </div>
  );
}

7.样式的使用

  1. 行内样式,如果 font-size 需要使用 fontSize 驼峰命名的方式
  2. 通过 className 用来绑定类名的方式
import "./index.css";
let style = {
  color: "lightblue",
  fontSize: "40px",
};
function App() {
  return (
    <div>
      {/* 行内样式的方式 */}
      <p style={{ color: "red", fontSize: "40px" }}>你好</p>
      {/* 也可以将绑定的行内样式提取出来,通过变量引用 */}
      <p style={style}>我好</p>
      {/* 通过className用来绑定类名 */}
      <p className="box">通过className用来绑定类名</p>
    </div>
  );
}

8.表单绑定

import { useState } from "react";

function App() {
  const [value, setValue] = useState("");
  const hanlderChange = (e) => {
    setValue(e.target.value);
  };
  return (
    <div>
      <input type="text" value={value} onChange={(e) => hanlderChange(e)} />
    </div>
  );
}

9. 获取 dom

  1. useRef 生成 dom 对象绑定到对应的标签上
  2. 通过 ref 去绑定 dom 元素
  3. 通过声明的元素的.current 获取 dom 元素
import { useRef } from "react";

function App() {
  // useRef生成dom对象绑定到对应的标签上
  // 默认值为null
  const dom = useRef(null);

  const getDom = () => {
    // 通过current属性
    console.log(dom.current);
  };
  return (
    <div>
      {/* 通过ref进行绑定 */}
      <p ref={dom}>获取p标签的dom元素</p>
      <button onClick={() => getDom()}>获取dom元素</button>
    </div>
  );
}
  1. 使用useRef声明的值,只会在页面加载时,进行初始化,不会在页面重新渲染时加载
  2. 重新设置useRef的值,页面不会重新渲染
  3. ref.current 不能作为其他hook的依赖项
    • 因为设置ref.current不会引起页面的重新渲染,所以hook的依赖项是检测不到的
import { useEffect, useRef, useState } from "react";
function App() {
  let [count, setCount] = useState(0);
  let time = useRef(new Date().getTime());
  const changeCount = () => {
    setCount((count += 1));
  };
  const updateTime = () => {
    const curTime = new Date().getTime();
    time.current = curTime;
  };
  console.log("zhixing ");

  useEffect(() => {
    // 只会初始化的时候执行,即使重新设置ref.current也不会执行
    console.log(time.current);
  }, [time.current]);

  return (
    <div className="App">
      {/* 当点击 count值发生改变的时候,time.current的值是不会发生改变的,
      也就是只会在初始化的时候,只会执行一次*/}
      <p>{time.current}</p>
      <button onClick={() => changeCount()}>{count}</button>
      <button onClick={() => updateTime()}>
        重新设置useRef的值,是不会引擎页面重新渲染的
      </button>
    </div>
  );
}

10.父子组件传值

  1. 通过给父组件标签添加属性
  2. 子组件通过 props 获取父组件的数据
  3. 我们把内容嵌套在子组件标签内部时,子组件会自动在名为 children 的 props 属性中接收该内容

子组件更改父组件的值

通过子组件调用父组件中的函数,去修改

  1. 将更改的函数通过进行传递
  2. 在子组件去调用传递过得函数
// 父组件
import Son from "./props/son";
import { useState } from "react";
function App() {
  let [name, updateName] = useState("小明");
  let changeName = (val) => {
    updateName(val);
  };
  return (
    <div>
      <h2>父组件</h2>
      <p>{name}</p>
      <Son name={name} onChangeName={changeName}>
        <span>在子组件内部嵌套内容</span>
      </Son>
      {/* <button onClick={() => changeName()}>
        父组件去更改父组件的值,传过去的子组件也会值也会变
      </button> */}
    </div>
  );
}

// 子组件
import { useState } from "react";
function Son(props) {
  let { name, children, onChangeName } = props;
  // let [childName, changeChildName] = useState(name);
  // 通过props.children拿到子组件嵌套的内容
  let changeParent = () => {
    // 调用父组件传递过来的方法
    onChangeName("子组件去更改父组件的值");
  };
  return (
    <div>
      <h2>子组件</h2>
      <p>通过props接受父组件的数据:{name}</p>
      <p>{children}</p>
      <button onClick={() => changeParent()}>子组件更改父组件的值</button>
    </div>
  );
}
export default Son;

11.兄弟组件传值

  1. 借助'状态提升'机制,通过父组件进行兄弟组件通信的方式
  • 兄弟 A 组件先通过把数据传递给父组件
  • 父组件拿到数据后,再将数据传递给兄弟组件 B
// 父组件
import Ason from "./a";
import Bson from "./b";

import { useState } from "react";

function App() {
  const [name, changeName] = useState("");
  let onChangeName = (val) => {
    console.log(val);
    changeName(val);
  };
  return (
    <div>
      <h2>父组件</h2>
      <Ason onChangeName={onChangeName}></Ason>
      <Bson name={name}></Bson>
    </div>
  );
}

// 组件A
import { useState } from "react";

function Ason(props) {
  const { onChangeName } = props;
  const [name, changeName] = useState("小明");
  let change = () => {
    changeName("小红");

    onChangeName("小红");
  };
  return (
    <div>
      <h2>组件A</h2>
      <p>{name}</p>
      <button onClick={change}>a组件中更改a组件中的值</button>
    </div>
  );
}
export default Ason;

// 组件B
function Ason(props) {
  const { name } = props;
  return (
    <div>
      <h2>组件B</h2>
      <p>{name}</p>
    </div>
  );
}
export default Bson;

12. 跨层传递数据 父=>孙

  1. 通过 createContext 创建上下文对象 ctx
  2. 在顶层组件通过 ctx.provider 组件提供数据
  3. 在底层组件通过 useContext 钩子函数获取数据
// 爷爷组件App组件
//  1.通过createContext创建上下文对象ctx
import { createContext } from "react";

import Acom from "./sun/a";

const msg = "app组件的值";
export const MsgContext = createContext();
function App() {
  return (
    <div>
      <h2>App组件</h2>
      {/* 通过自定义组件的方式,value传递数据值 */}
      <MsgContext.Provider value={msg}>
        <Acom></Acom>
      </MsgContext.Provider>
    </div>
  );
}

// 父组件A
// 引入孙组件B
import Bcom from "./b";

export default function Acom() {
  return (
    <div>
      <h2>父组件</h2>
      <Bcom></Bcom>
    </div>
  );
}

// 孙组件B
import { useContext } from "react";
// 引入爷爷组件导出的MsgContext,通过MsgContext获取爷爷组件上的值
import { MsgContext } from "../../App";
export default function Bcom() {
  // 在根组件通过useContext获取createContext上绑定的值
  const msg = useContext(MsgContext);
  return (
    <div>
      <h2>孙子组件</h2>
      <p>{msg}</p>
    </div>
  );
}

13. useEffect

1.基础用法

  • useEffect 是一个 react 的 hook 函数,用于在 react 组件中,创建不是由渲染本身引起的操作,比如发送 ajax 请求

  • useEffect 接受两个参数

    • useEffect(()=>{},[])
    • 第一个参数是一个函数,我们可以把它叫做副作用函数,在函数内部可以放置要执行的操作
    • 第二个参数是一个数组,在数组内放置依赖项,不同的依赖性会影响第一个参数的执行
      • 当是一个空数组时,副作用函数只会在组件渲染完毕之后执行一次
  • 案例,当页面初始化时,发起 ajax 请求

import axios from "axios";
import { useEffect, useState } from "react";
const url = "http://geek.itheima.net/V1_0/channels";
function App() {
  let [list, setList] = useState([]);
  let getList = async () => {
    let { data } = await axios.get(url);
    console.log(data);
    setList(data.data.channels);
  };
  useEffect(() => {
    getList();
  }, []);
  return (
    <div>
      <ul>
        {list.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

2. useEffect 第二个参数的三种状态

  1. 默认不传,初始化+组件数据发生变更时都会执行
  2. 传入一个空数组,只会在初始化时执行
  3. 传入一个特定依赖项,初始化+依赖项变化时执行
import { useEffect, useState } from "react";

function App() {
  let [name, changeName] = useState("小明");
  let [count, changeCount] = useState(0);
  let hanlderName = () => {
    changeName("小红");
  };
  let hanlderCount = (val) => {
    changeCount((count += val));
  };
  // 1.默认第二个参数不传
  // useEffect(() => {
  //   console.log("初始化+组件数据更新时都会执行");
  // });

  // 2. 传入一个空数组,只会在初始化时执行
  // useEffect(() => {
  //   console.log("只会在初始化时执行");
  // }, []);

  // 3.传入一个特定依赖项,初始化+依赖项变化时执行
  useEffect(() => {
    console.log("初始化+依赖项变化时执行");
    // 初始化+只有name发生改变时才会执行
  }, [name]);
  return (
    <div>
      <p>{name}</p>
      <p>{count}</p>
      <button onClick={() => hanlderName()}>更改name值</button>
      <button onClick={() => hanlderCount(1)}>更改count的值</button>
    </div>
  );
}

3. useEffect 清除副作用

  1. 在 useEffect 中编写的由渲染本身引起的对接组件外部的操作,社区经常把它称之为副作用操作
  • 比如在 useEffect 中开启一个定时器,我们想在组件卸载后,将定时器清除掉,这个过程就是清理副作用
  1. 清理副作用的使用场景:清理副作用最常见的执行时机组件卸载时自动执行
// 语法
useEffect(() => {
  return () => {
    // 清理副作用逻辑
  };
}, []);

import { useState, useEffect } from "react";

function Son() {
  useEffect(() => {
    let timer = setInterval(() => {
      console.log("定时器执行");
    }, 1000);
    // 卸载组件时候,清除定时器,清理副作用逻辑
    return () => {
      clearInterval(timer);
    };
  }, []);
  return <div>son子组件</div>;
}

function App() {
  let [isFlag, setFlag] = useState(true);
  return (
    <div>
      {isFlag && <Son></Son>}
      <button onClick={() => setFlag((isFlag = false))}>卸载子组件</button>
    </div>
  );
}

14. useReducer

  • 三个参数
    • 参数1:事件处理函数
    • 参数2:数据的初始值
    • 参数3:针对初始值,进行限定
  • 案例: 结合useContext实现嵌套组件,修改父组件中的值
import { useReducer, createContext, useContext } from "react";

// // 创建全局的Context对象
const MyContext = createContext();

const Son1 = () => {
  const { count, dispatch } = useContext(MyContext);
  return (
    <div>
      <p>{count}</p>
      <button
        onClick={() => {
          dispatch({ type: "RIDE", playload: 2 });
        }}
      >
        乘法
      </button>
      <Reset count={count} dispatch={dispatch}></Reset>
    </div>
  );
};
const Reset = () => {
  const { count, dispatch } = useContext(MyContext);
  return (
    <div>
      <p>{count}</p>
      <button
        onClick={() => {
          dispatch({ type: "RESET", playload: 0 });
        }}
      >
        重置
      </button>
    </div>
  );
};
function App() {
  const reducer = (state, action) => {
    console.log("触发执行", action);
    switch (action.type) {
      case "ADD":
        return { count: (state.count += action.playload) };
      case "SUB":
        return { count: (state.count -= action.playload) };
      case "RIDE":
        return { count: state.count * action.playload };
      case "RESET":
        return { ...initState, count: Math.abs(parseInt(initState.count)) };
      default:
        return state;
    }
  };
  const initState = { count: -9.123 };
  // 例如:针对默认参数,进行处理
  // 例如:需要取绝对值并且取整
  const initAction = (initState) => {
    return { count: Math.abs(parseInt(initState.count)) };
  };
  const [state, dispatch] = useReducer(reducer, initState, initAction);
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "ADD", playload: 2 })}>+2</button>
      <button onClick={() => dispatch({ type: "SUB", playload: 1 })}>-1</button>
      {/* 必须通过value属性来进行传递 */}
      <MyContext.Provider value={{ ...state, dispatch }}>
        <Son1></Son1>
      </MyContext.Provider>
    </div>
  );
}
  • 在reducer函数中,我们通过action.type的值,去指定不同的操作
  • initState 作为初始数据
  • initAction 作为针对初始数据的处理
  • 通过结合createContext将当前的state和dispatch进行向下传递
  • 在子组件或者孙子组件中,通过获取state显示数据,调用dispatch传递不同的type调用reducer进行数据处理

15. 自定义 Hooks 实现

  1. 声明一个以 use 打头的函数
  2. 在函数体内封装可复用的逻辑
  3. 把组件中用的状态或函数通过 return 导出一个对象或者数组
  4. 在哪个组件中使用这个逻辑,就执行这个函数,结构出来用到的状态或者函数
  • 案例,盒子隐藏和显示
import { useState } from "react";

function useToggle() {
  let [isFlag, changeFlag] = useState(true);
  let hanlderChange = () => {
    changeFlag(!isFlag);
  };
  return {
    isFlag,
    hanlderChange,
  };
}

function App() {
  let { isFlag, hanlderChange } = useToggle();
  return (
    <div>
      {isFlag && <div>显示或隐藏</div>}
      <button onClick={() => hanlderChange()}>点击切换</button>
    </div>
  );
}

16. useMemo 在组件重新渲染的时候,缓存结果,类似 vue 的 computed

import { useMemo, useState } from "react";

function dip(n) {
  console.log("斐波那锲"); // 当count2发生改变时,也会打印
  if (n < 3) return 1;
  return dip(n - 1) + dip(n - 2);
}

function App() {
  let [count1, setCount1] = useState(0);
  let [count2, setCount2] = useState(0);
  // let result = dip(count1);

  // 通过useMemo,只有当count1发生改变时,才会执行
  let result = useMemo(() => {
    return dip(count1);
  }, [count1]);
  return (
    <div>
      <button onClick={() => setCount1(count1 + 1)}>{count1}</button>
      <button onClick={() => setCount2(count2 + 1)}>{count2}</button>
      {result}
    </div>
  );
}

17. React.memo

  1. 当父组件中的数据发生改变时,子组件也会进行重新渲染,
  • memo 允许子组件在没有 props 的情况下,不进行渲染
  • 使用 memo 子组件不会进行更新
  1. prop 的比较机制
  • 在使用 memo 缓存组件后,React 会对子组件的 prop 使用 Object.is()进行比较新,旧值,如果返回 true 表示没有变化

  • prop 简单数据类型

    • Object.is(2,2) true 没有变化
  • prop 复杂数据类型

    • Object.is([],[]) false,有变化,react 只关心引用是否变化
import { memo, useMemo, useState } from "react";

const MemoSon = memo(function Son({ sum }) {
  console.log("子组件重新渲染");
  return <div>子组件</div>;
});

function Son(params) {
  console.log("子组件");
  return <div>子组件</div>;
}
function App() {
  const [sum, setSum] = useState(0);
  // const list = [1, 2, 3];
  const list = useMemo(() => {
    return [1, 2, 3];
  }, []);
  return (
    <div>
      {/* <Son></Son> */}
      {/*1.简单数据类型,当prop的值发生改变时,子组件会重新渲染 */}
      {/* <MemoSon sum={sum}></MemoSon> */}
      {/*2.简单数据类型,当prop的值不发生改变时,子组件不会重新渲染 */}
      {/* <MemoSon sum={100}></MemoSon> */}
      {/*3.复杂数据类型,子组件也会重新渲染,因为每次父组件数据发生改变的时候,父组件会重新渲染,实际上形成新的prop引用,memo只会关心引用的变化 */}
      {/* <MemoSon list={list}></MemoSon> */}
      {/* 4.复杂数据类型,如果数据不变,但不想让子组件重新渲染,可以使用useMemo,将数据进行缓存 */}
      <MemoSon list={list}></MemoSon>
      <button onClick={() => setSum(sum + 1)}>{sum}</button>
    </div>
  );
}

18. useCallback

  1. 在组件多次渲染的时候,缓存函数

    • 也就是现在 props 现在传递的是函数
import { useState, useCallback, memo } from "react";
const MemoSon = memo(function Son({ onChange }) {
  console.log("子组件更新了");
  const hanlderChange = (val) => {
    onChange(val);
  };
  return (
    <div>
      <input type="text" onChange={(e) => hanlderChange(e.target.value)} />
    </div>
  );
});
function App() {
  const [num, setNum] = useState(0);

  const hanlderChange = useCallback((val) => {
    console.log(val);
  }, []);
  return (
    <div>
      <MemoSon onChange={hanlderChange}></MemoSon>
      <button onClick={() => setNum(num + 1)}>{num}</button>
    </div>
  );
}

19. 在父组件中获取子组件的 Dom 元素

通过 forwardRef

import { forwardRef, useRef } from "react";
// function Son() {
//   return <input type="text" />;
// }

const Son = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />;
});

function App() {
  const sonRef = useRef(null);
  const getSonBom = () => {
    console.log(sonRef); // 拿到子组件的dom
    sonRef.current.focus();
  };
  return (
    <div>
      <Son ref={sonRef}></Son>
      <button onClick={getSonBom}>获取子组件的Dom</button>
    </div>
  );
}

20.在父组件中调用子组件的方法,

通过 useImperativeHandle

import { forwardRef, useRef, useImperativeHandle } from "react";

const Son = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const hanlderFocus = function() {
    inputRef.current.focus();
  };
  useImperativeHandle(ref, () => {
    return {
      hanlderFocus,
    };
  });
  return <input type="text" ref={inputRef} />;
});

function App() {
  const sonRef = useRef(null);
  const getSonMethod = () => {
    console.log(sonRef.current);
    sonRef.current.hanlderFocus();
  };
  return (
    <div>
      <Son ref={sonRef}></Son>
      <button onClick={getSonMethod}>调用子组件的方法</button>
    </div>
  );
}
export default App;