React学习笔记

465 阅读10分钟

学习视频:b站:黑马程序员前端React18入门到实战视频教程

简介

用于构建Web和原生交互界面的库

优势

相较于传统基于DOM开发的优势

  • 组件化的开发方式:提高开发效率和组件复用性
  • 不错的性能
    • 采用虚拟dom

相较于其它前端框架的优势

  • 丰富的生态
  • 跨平台支持
    • React Native和Expo可使用React构建Andrio、iOS等应用程序

开始

创建

使用cra创建项目

create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用
npm i -g create-react-app
npx create-react-app 项目名
npx - Node.js工具命令,查找并执行后续的包命令
项目目录:

  • src
    • App.js根组件
    • index.js入口文件

app引入到index,再渲染到public/index.html

使用vite创建项目

npm create vite@latest 项目名 -- --template react

JSX

JSX是JavaScript和XMl(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中构建UI的方式
优势:

  • HTML的声明式模版写法
  • JavaScript的可编程能力

本质:
JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中使用
通过babel编译

js表达式

使用{}识别js中的表达式:

  • 使用引号传递字符串{'str'}
  • 使用JS变量{count}
  • 函数调用和方法调用{new Date().time()}
  • 使用JavaScript对象<div style={{color: 'red'}}></div>

列表渲染

const list = [
  {id: 1, name: 'John'},
  {id: 2, name: 'John1'},
  {id: 3, name: 'John2'},
]
function App() {
  return (
    <div className="App">
      <ul>
        {list.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

条件渲染

const isLogin = false;
function App() {
  return (
    <div className="App">
      <p>{isLogin && 'true显示'}</p> 
      <p>{isLogin ? <span>true显示</span> : <span>false显示</span>}</p>
    </div>
  );
}
function typeChange(type) {
  if (type === 1) {
    return <div>111</div>;
  } else if (type === 2) {
    return <div>222</div>;
  } else {
    return <div>333</div>;
  }
}
function App() {
  return (
    <div className="App">
      {typeChange(2)}
    </div>
  );
}

事件绑定

React中的事件绑定,通过语法 on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

function btnClick() {
  console.log('btnClick')
}
function btnClick2(e) {
  // 事件对象
  console.log(e)
}
function btnClick3(name) {
  console.log(name)
}
function btnClick4(e, name) {
  console.log(e, name)
}
function App() {
  return (
    <div className="App">
      <button onClick={btnClick}>onClick</button>
      <button onClick={btnClick2}>onClick2</button>
      <button onClick={() => btnClick3('name')}>onClick3</button>
      <button onClick={(e) => btnClick4(e, 'name')}>onClick4</button>
    </div>
  );
}

组件

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

基础使用

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

function MyButton() {
  return <button>MyButton</button>
}
function App() {
  return (
    <div className="App">
      <MyButton />
      <MyButton></MyButton>
    </div>
  );
}

useState声明状态数据

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果
和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图

import { useState } from 'react';
function App() {
  // 调用useState方法创建一个状态变量,并返回该变量的当前值和一个更新该变量的函数。
  const [count, setCount] = useState(0);

  const handleClick = () => {
    // 在React中状态被认为是只读的,我们应该始终`替换它而不是修改它`,
    // 直接修改状态不能引发视图更新
    setCount(count + 1);
  }
  return (
    <div className="App">
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}
import { useState } from 'react';
function App() {
  // 调用useState方法创建一个状态变量,并返回该变量的当前值和一个更新该变量的函数。
  const [count, setCount] = useState({
    num: 1
  });

  const handleClick = () => {
    // 对于对象类型的状态变量,应该始终给set方法一个`全新的对象` 来进行修改
    setCount({
      ...count,
      num: count.num + 1
    });
  }
  return (
    <div className="App">
      <button onClick={handleClick}>{count.num}</button>
    </div>
  );
}

组件基础样式

.btn {
  background-color: aqua;
}
import './index.css'
const btn = {
  backgroundColor: 'red'
}
function App() {
  return (
    <div className="App">
      {/* class类名控制 */}
      <button className='btn'>111</button>
      {/* 行内样式-不推荐 */}
      <button style={btn}>222</button>
    </div>
  );
}

表单受控绑定-类似双向数据绑定

image.png

import {useState} from "react";
function App() {
  const [inputValue, setInputValue] = useState('')
  return (
    <div className="App">
      <input value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
    </div>
  );
}

获取dom

import {useRef} from "react";
function App() {
  const inputDom = useRef(null);
  const btnClick = () => {
    // 渲染完才可以获取dom
    console.log(inputDom.current);
  }
  return (
    <div className="App">
      <input type="text" ref={inputDom}/>
      <button onClick={() => btnClick()}>log dom</button>
    </div>
  );
}

组件通信

组件间的数据传递

父传子

function Son(props) {
  console.log(props.children)
  return <div>{props.sonName}</div>
}
function App() {
  const sonName = '我是子组件'
  return (
    <div className="App">
      <Son sonName={sonName}>
        在自子组件中嵌套的内容会在props的children属性接收
      </Son>
    </div>
  );
}

props可以传递任意合法数据:数字、字符串、布尔值、数组、对象、函数、JSX
props是只读的:父组件传递的数据只能由父组件修改

子传父

import {useState, useEffect} from 'react';
function Son({onGetSonMsg}) {
  // useEffect 钩子旨在处理副作用,应用于渲染后需要发生的状态更新
  useEffect(() => {
    // 如果直接使用会报错,因为会在渲染时更新父组件的数据
    // 这样违反了react组件生命周期规则
    // React 的单向数据流不允许子组件在渲染过程中对父组件产生副作用
    onGetSonMsg('子组件传来的数据');
  })
  return <div>子组件</div>
}
function App() {
  const [sonMsg, setSonMsg] = useState('')
  const getSonMsg = (msg) => {
    setSonMsg(msg)
  }
  return (
    <div className="App">
      {sonMsg}
      <Son onGetSonMsg={getSonMsg}></Son>
    </div>
  );
}

useEffect

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

  • 参数1是副作用函数
  • 参数二可选,放置依赖项 | 依赖项 | 副作用功函数的执行时机 | | --- | --- | | 没有依赖项 | 组件初始渲染 + 组件更新时执行 | | 空数组依赖 | 只在初始渲染时执行一次 | | 添加特定依赖项 | 组件初始渲染 + 依赖项变化时执行 |
import {useEffect, useState} from 'react';
function App() {
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log('count变化了');
  }, [count]);
  return (
    <div className="App">
      {count}
      <button onClick={() => setCount(count + 1)}>count++</button>
    </div>
  );
}

副作用

副作用操作:在useEffect中编写的由渲染本身引起的对接组件外部的操作

import {useEffect, useState} from 'react';
function B() {
  let intervalId = setInterval(() => {
    console.log('定时器执行中');
  }, 2000)
  useEffect(() => {
    return () => {
      // 清除副作用(组件卸载时)
      clearInterval(intervalId)
    }
  })
  return <div>B</div>
}
function App() {
  const [showB, setShowB] = useState(true)
  return (
    <div className="App">
      {showB && <B></B>}
      <button onClick={() => setShowB(false)}>销毁B</button>
    </div>
  );
}

兄弟组件通信

状态提升:兄弟1 =》父组件 =》兄弟2

import {useState, useEffect} from 'react';

function Brother1({onGetBrother1Msg}) {
  useEffect(() => {
    onGetBrother1Msg('兄弟组件1传递给兄弟组件2的信息')
  })
}
function Brother2({brother1Msg}) {
  return <div>{brother1Msg}</div>
}
function App() {
  const [brother1Msg, setBrother1Msg] = useState('');
  return (
    <div className="App">
      <Brother1 onGetBrother1Msg={setBrother1Msg}></Brother1>
      <Brother2 brother1Msg={brother1Msg}></Brother2>
    </div>
  );
}

跨层通信

父子通信也适用

import {createContext, useContext} from 'react';
function A () {
  return <div>
    <B></B>
  </div>
}
function B () {
  // 底层:使用useContext接收信息
  const msg = useContext(crtMsg)
  return <div>{msg}</div>
}
// 使用createContext创建上下文对象
const crtMsg = createContext()
function App() {
  const msg = '这是顶层的信息'
  return (
    <div className="App">
      {/* 顶层提供数据 */}
      <crtMsg.Provider value={msg}>
        <A></A>
      </crtMsg.Provider>
    </div>
  );
}

hook

自定义hook

import {useState} from 'react';
// 声明一个以use打头的函数
function useToggle() {
  const [showB, setShowB] = useState(true)
  const toggle = () => setShowB(!showB)
  // 把组件中用到的状态或者回调return出去(以对象或者数组)
  return {
    showB,
    toggle
  }
}
function App() {
  // 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
  const {showB, toggle} = useToggle()
  return (
    <div className="App">
      {showB && 'bbbbbbbbbbb'}
      <button onClick={toggle}>销毁B</button>
    </div>
  );
}

react hooks使用规则

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

redux

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
**作用:**通过集中管理的方式管理应用的状态
优势:

  • 独立于组件,无视组件之间的层级关系,简化通信问题
  • 单项数据流清晰,易于定位bug
  • 调试工具配套良好,方便调试

三个核心概念:

  • state: 一个对象 存放着我们管理的数据
  • action: 一个对象 用来描述你想怎么改数据
  • reducer: 一个函数 根据action的描述更新state

配套工具

npm i @reduxjs/toolkit react-redux

  • Redux Toolkit(RTK):官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
  • react-redux : 用来 链接 Redux 和 React组件 的中间件

工作目录举例

image.png

修改、使用store中的数据

import {createSlice} from "@reduxjs/toolkit"

const counterStore = createSlice({
  name: 'counter',
  // 初始化state
  initialState: {
    count: 0
  },
  // 修改状态的方法,支持同步修改
  reducers: {
    inscrement(state) {
      state.count += 1
    },
    decrement(state) {
      state.count -= 1
    },
    // action传参
    incrementByAmount(state, action) {
      state.count += action.payload
    }
  }
})

// 解构出actionCreater函数
const {inscrement, decrement} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer

// 导出actionCreater
export {inscrement, decrement}
// 导出reducer
export default reducer
import { configureStore } from "@reduxjs/toolkit";
// 导入子模块
import counterReducer from './modules/counterStore';

const store = configureStore({
  reducer: {
    counterReducer
  }
})

export default store
// 导入store
import store from './store';
// 导入store提供组件Provider
import { Provider } from 'react-redux'


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    // 提供store数据
  <Provider store={store}>
    <App />
  </Provider>
);
import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { inscrement, decrement, incrementByAmount } from "./store/modules/counterStore";
function App() {
  // 使用state中的数据
  const {count} = useSelector(state => state.counterReducer)
  // 创建dispatch函数,用于修改数据
  const dispatch = useDispatch()
  return (
    <div className="App">
      <button onClick={() => dispatch(decrement())}>-</button>
      {count}
      <button onClick={() => dispatch(inscrement())}>+</button>
      {/* action传参 */}
      <button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
    </div>
  );
}

export default App;

给state设置异步请求数据

import { createSlice } from "@reduxjs/toolkit";
import axios from 'axios';

const channelStore = createSlice({
  name: "channel",
  initialState: {
    channelList: []
  },
  reducers: {
    setChannelList(state, action) {
      state.channelList = action.payload;
    }
  }
})

// 封装一个函数 在函数中return一个新函数 在新函数中封装异步
// 得到数据之后通过dispatch函数 触发修改
const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels');
    dispatch(setChannelList(res.data.data.channels))
  }
}

// 解构actions
const { setChannelList } = channelStore.actions
// 获取reducer
const reducer = channelStore.reducer

export {fetchChannelList}
export default reducer

store/index.js引入

import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { fetchChannelList } from "./store/modules/channelStore";

import {useEffect} from 'react'
function App() {
  // 使用state中的数据
  const {channelList} = useSelector(state => state.channelReducer)
  // 创建dispatch函数,用于修改数据
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [])
  return (
    <div className="App">
      {JSON.stringify(channelList)}
    </div>
  );
}

export default App;

浏览器调试工具

image.png

reactRouter

安装

npm i react-router-dom

使用

目录

image.png

const Artical = () => {
  return (
    <div>artical</div>
  )
}

export default Artical
import Artical from "../pages/Artical";
import Login from "../pages/Login";

import { createBrowserRouter } from "react-router-dom";

const router = createBrowserRouter([
  {
    path: '/login',
    element: <Login />
  },
  {
    path: '/article',
    element: <Artical />
  }
])

export default router
import {RouterProvider} from 'react-router-dom'
import router from './router/index.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    {/* <App /> */}
    <RouterProvider router={router} />
  </React.StrictMode>,
)

路由导航

import { Link, useNavigate } from "react-router-dom"

const Login = () => {
  const navigate = useNavigate()
  return (
    <div>
      Login
      {/* 声明式导航 */}
      <Link to='/article'>跳转到/article</Link>
      {/* 编程式导航 */}
      <button onClick={() => navigate('/article')}>跳转到/article</button>
    </div>
  )
}

export default Login

路由传参

import { useNavigate } from "react-router-dom"

const Login = () => {
  const navigate = useNavigate()
  return (
    <div>
      Login
      {/* searchParams传参 */}
      <button onClick={() => navigate('/article?id=100')}>searchParams传参</button>
      {/* params传参 */}
      <button onClick={() => navigate('/article/200')}>params传参</button>
    </div>
  )
}

export default Login
import { useParams, useSearchParams } from "react-router-dom"

const Artical = () => {
  {/* searchParams传参 */}
  const [params] = useSearchParams();
  let id1 = params.get('id');
  // params传参
  const param2 = useParams();
  let id2 = param2.id;
  return (
    <div>
      artical
      <br />
      {id1}
      <br />
      {id2}
    </div>
  )
}

export default Artical
  // searchParams传参
  {
    path: '/article',
    element: <Artical />
  },
  // params传参
  {
    path: '/article/:id',
    element: <Artical />
  }

嵌套路由

{
    path: '/login',
    element: <Login />,
    // 嵌套路由
    children: [
      {
        // 默认路由
        index: true,
        element: <Child1 />,
      },
      {
        path: 'child2',
        element: <Child2 />,
      }
    ]
  }
import { Outlet } from "react-router-dom"

const Login = () => {
  return (
    <div>
      Login
      {/* 二级路由渲染位置 */}
      <Outlet />
    </div>
  )
}

export default Login

404路由配置

  1. 准备一个NotFound组件
const NotFound = () => {
  return (
    <div>404</div>
  )
}
export default NotFound;
  1. 在路由表数组的末尾,以*号作为路由path配置路由
  {
    path: '*',
    element: <NotFound/>
  }

2种路由模式

各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, ReactRouter分别由 createBrowerRouter 和 createHashRouter 函数负责创建

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件需要
hashurl/#/login监听hashChange事件不需要

原文链接:前端路由hash模式以及history模式详解

hash

// 使用 addEventListener 监听 hashchange 事件:
window.addEventListener('hashchange', function() {
  console.log('hash值被修改了')
}, false);

// 使用 onhashchange 事件处理程序
function locationHashChanged() {
	if (location.hash === '#/about') {
		console.log("欢迎进入about页面");
	}
}
window.onhashchange = locationHashChanged;

history

history 是 HTML5 提供的新特性,允许开发者直接更改前端路由,也就是更改 url 地址而无需向后端发送 http 请求。

history.pushState() 向当前浏览器历史中添加记录,方法接收三个参数:

  • state:一个对象,popState 事件触发时,state 对象会传入回调函数。如无需传参,则设置为 null
  • title:新页面的标题,但是所有浏览器目前都忽略这个值,因此可以设置为空字符串 "" 或者 null
  • url:新的网址地址,必须与当前页面处于同一个域下,浏览器的地址栏将显示这个网址

window.onpopstate 事件用来监听浏览历史记录变化,在同一页面的两个历史条目直接跳转触发

loadsh排序库:
npm i lodash
classnames库:
npm install classnames

import classnames from 'classnames';

<span
  className={classnames('nav-item', {'active': type === item.type})}
>
  text
</span>

uuid: 生成唯一id
dayjs:时间格式化
json-server
模拟json数据
前端接口神器之 json-server 详细使用指南