React从0到1学习记录、学就完了

119 阅读16分钟

一、事件绑定

<button onClick = ()=>handleClick()> click me </button>

二、组件是什么

  1. 可以相互嵌套、可以多次复用
  2. 在React 中,一个组件就是首字母大写的函数,渲染只需要把组件当成标签书写即可:
// 定义组件
function Button() {
 return <button> click me! </button>
}
// 或者
const Button = ()=> {
 return <button> click me! </button>
}
// 使用组件
function App() {
    return(
     <div className=”app”>
       <Button/>
       // 或者
       <Button></Button>
    </div>
    )
}

三、useState 基础使用

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

const [count,setCount] = useState(0);
  1.  useState 是一个函数,返回值是一个数组;
  2.  数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态变量;
  3.  useState的参数将作为count的初始值
// 示例代码:
import { useState } from ‘react’;
function App() {
    // 调用useState 添加一个状态变量
    const [ count, setCount] = useState;
    // 点击回调
    const handleClick = ()=> {
      setCount(count+1); // 用传入的新值修改count
    }
    return(
      <div>
        <button onClick={handleClick}>{count}</button>
    </div>
    )
}
export default App

修改状态的规则:

  1.  状态不可变

在React中,状态认为是可读的,应该始终替换它而不是修改它,直接修改状态不能引发视图更新。

直接修改:count++    正确做法: setCount(count+1)

  1. 修改对象状态

规则:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。

const [form,setForm] = useState({
  name:’jack’
})
const handleChangeName = ()=>{
    setForm({
      ...from,
      name:’john’,
    })
}

四、组件基础样式

  1. 行内样式(不推荐)
<div style={color:’red’}> this is div </div>
  1. class类名控制
import ‘./index.css ’;
<div className = “foo”> this is div </div>
  1. classnames优化类名控制
classnames是一个简单的JS库,可以方便通过条件动态控制class类名的显示:如下
className={classNames(‘nav-item ’,{active:type===item.type}});

五、受控表单绑定

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

  1. 准备一个react状态值
const [value,setValue] = useState(‘’);
  1. 通过value属性绑定状态,通过onChange属性绑定状态同步的函数
<input type=”text”value=[value] onChange=(e)=>setValue(e.target.value)};

六、React 中获取DOM元素

在react组件中获取/操作dom,需要使用useRef 钩子函数,分为两步:

  1. 使用useRef 创建ref对象,并与JSX绑定
import { useRef } from “react”;
const inputRef = useRef(null);
<input type=”text” ref={inputRef} />
  1. 在dom可用时(渲染完成),通过inputRef.current拿到dom对象
console.log(inputRef.current);

七、组件通信

概念:组件通信就是数组之间的数据传递

父传子:实现步骤

  1. 父组件传递数据-在子组件标签上绑定属性
  2. 子组件接收数据-子组件通过props参数接收数据
// 示例代码:子组件
function Son(props){
 console.log(props); // {name:’父组件传递的数据’}
 return <div>this is son,{props.name}</div>
}
// 父组件
function App(){
    const name = ‘this is app name’;
    return (
        <div>
          <Son name={name} />
        </div>
    )
}
export default App

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

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

<Son>
  <span>this is span</span>
</Son>
// 控制台:
props
children:<span/>
  New entry: ””

子传父:实现步骤

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

// 示例代码:父组件
function App(){
    const getMsg = (msg)=>{
     console.log(msg)
    };
    return (
        <div>
          <Son onGetMsg={getMsg } />
        </div>
    )
}
// 子组件
function Son({onGetMsg}){
     const sonMsg = “this is son msg”;
     return (
        <div>
          <button onClick={()=>onGetMsg(sonMsg )}> send </button >
        </div>
    )
}

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

A、B 是兄弟关系,C是父组件: A -> C -> B  或者  B -> C -> A

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

App(顶层组件) -A(中间组件) - B(底层组件)

实现步骤:App - > B

1. 使用createContext方法创建一个上下文对象Ctx;

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

在底层组件(B)中通过useContext钩子函数获取数据;

八、useEffect的概念理解

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

说明:上面组件没有发生任何的用户事件,组件渲染完毕之后就需要和服务器要数据,整个过程属于“只由渲染引起的操作”。

语法: useEffect(()=>{ },[]);

参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作。

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

useEffect - 依赖项参数说明

根据传入的依赖项不同, 会有不同的执行表现:

1. 没有依赖项:组件初始渲染+组件更新时执行

2. 空数组依赖:只有初始渲染时执行一次

3. 添加特定依赖项:组件初始渲染+特性依赖项变化时执行

useEffect - 清除副作用

在useEffect中编写的由渲染本身引起的对接组件外的操作,社区也经常把它叫做副作用操作, 比如在useEffect中开启了一个定时器,在组件卸载时把这个定时器清除掉,这个过程就是清理副作用

// 示例代码:
useEffect(()=>{
  // 实现副作用操作逻辑
  const timer = setInterval(()=>{
    Console.log(‘定时器执行中...’);
  },1000)
  return()=>{
    // 清除副作用
    clearInterval(timer);
  }   
},[]);

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

九、自定义Hook函数

概念:自定义Hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

封装自定义Hook通用思路:

  1. 声明一个以use打头的函数
  2. 在函数内封装可复用的逻辑(只要是可复用的逻辑)
  3. 把组件中用到的状态或者回调return出去
  4. 在那个组件中要用到这个函数,就执行这个函数,解构出来状态和回调进行使用
// 示例代码:
import {useState} from ‘react’;
// 封装函数
function useToggle(){
    // 可复用的逻辑代码
    const {value,setValue}= useState(true);
    const toggle=()=>setValue(!value);
    // 把要使用的状态和回调函数return 出去
    return {
        value,toggle
    }
}
// 使用封装的函数
function App(){
    const {value,toggle}= useToggle();
    return (
        <div>
          {value && <div>this is div </div>}
          <button onClick={toggle}>toggle</button>
        </div>
    )
}
export default App

React Hooks使用规则****

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

十、什么是Redux

Redux是React最常用的集中状态管理工具,类似于Vue中的pinia(Vuex),可以独立于框架运行Redux快速体验:不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器。如:  - 0 +

使用步骤:

1. 定义一个reducer函数(根据当前想要做的修改返回一个新的状态);

function reducer(state={count:0},action) { }

2. 使用createStore方法传入reducer函数 生成一个store实例对象

const store = Redux.createStore(reducer);

3. 使用store实例的subscribe方法订阅数据变化(数据变化,可以得到通知);

store.subscribe(()=>{

console.log( state数据变化了 ,store.getState());

})

4. 使用store实例的dispatch方法提交action对象触发数据变化(告诉  reducer你想怎么改数据);

store.dispatch({ type: ADD })

5. 使用store实例的getState方法获取最新的状态更新到视图中;

store.getState()

Redux管理数据流程梳理:

Redux把整个数据修改的流程分成了三个核心概念,分别是:state、action和reducer。

  1. state- 一个对象,存放着我们管理数据的状态;
  2. action- 一个对象,来描述你想怎么改数据;
  3. reducer- 一个函数,根据action的描述生成一个新的state;

十一、Redux和React-环境准备

在React中使用Redux,官方要求安装两个其他插件-Redux Toolkit和react-redux

  1. Redux Toolkit(RTK)-官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式

    • 简化store的配置方式

    • 内置immer支持可变式状态修改

    • 内置thunk更好的异步创建

  2. react-redux - 用来链接Redux和React组件的中间件

    Redux -> 获取状态 -> React组件

    React组件 -> 更新状态 -> Redux

配置基础环境

1.使用CRA快速创建React项目

  npx create-react-app react-redux

2.安装配置工具

  npm i @reduxjs/toolkit react-redux

3.启动项目

  npm run start

十二、Redux与React-实现counter

store目录结构设计

  • 通常集中状态管理的部分都会创建一个单独的‘store’目录
  • 应用通常会分多个store子模块 所以创建一个‘modules’目录,在内部编写业务分类的子store;
  • store中的入口文件index.js的作用是组合modules’中所有的子模块,并导出store;

1. 首先创建一个‘modules’目录子模块

// 新增 ./modules/counterStore.js文件 - 示例代码:
import { createSlice }from ‘@Reduxjs/toolkit ’;
const counterStore = createSlice ({
    name: ‘counter’,
    // 数据状态 state
    initialState:{
      count: []
    }
}),
reducers: {
  // 同步修改方法
  addToNum(state, action) {
    state.count = action.payload
   }
}
// 解构actionCreater函数
const { addToNum } = counterStore.actions
// 编写异步
const getCount = ()=>{
    return async(dispatch)=>{
        // 异步请求
        const res = await axios.get(‘http/localhost:8888/ka’);
        // 触发同步reducer
        dispatch(setCounterList(addToNum(res.data)));
    }
}
// 导出reducer
const reducer = counterStore.reducer
export default reducer

2. 使用Redux Toolkit创建counter

 // store中的**入口文件index.js - 示例代码:
import { configureStore }from ‘@Reduxjs/toolkit ’;
import counterReducer  from ‘./modules/counterStore’;

// 创建根store组合子模块
const store = configureStore({
  reducer:{
    counter: counterReducer
  }
})
export default store
  1. 为React组件注入store
// react-redux负责把Redux和React组件链接起来,内置Provider组件,通过store参数把创建好的store实例注入到应用中,链接正式建立。
// index.js文件-示例代码:
import store from ‘./store ’;
import { Provider } from ‘react-redux’;
const root = ReactDOM.createRoot(document.getElementById(‘root’))
root.render(
  <Provider store={store}>
     <App />
  </Provider>
)

4. React组件使用store中的数据

// 在React组件使用store中的数据,需要用到一个钩子函数 - useSelector,它的作用是把store中的数据映射到组件中,使用示例如下:
import { useSelector } from ‘react-redux’;
const { count } = useSelector(state=>state.counter);

5. React组件修改store中的数据

// React组件修改store中的数据需要借助另外一个**hook函数- useDispatch**,**它的作用是生成提交action对象的dispatch函数**,使用样例如下:
import { useDispatch } from ‘react-redux’;
import { inscrement , decrement }from ‘./store/modules/counterStore’;
function App(){
    const { count } = useSelector(state=>state.counter);
    const dispatch =  useDispatch();
    return (
        <div className=”App”>
            <button onClick={()=>dispatch (decrement())}> - </button>
            {count}
            <button onClick={()=>dispatch (inscrement ())}> + </button>
        </div>
    )
}
export default App

十三、Redux与React-提交action传参

在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传参参数会被传递到action对象payload属性上

  1. 在 ./store/modules/counterStore文件添加:
// 修改状态的方法,同步方法 支持直接修改
reducers:{
    addToNum(state,action) {
       State.count = action.payload;
    }
}
export { addToNum };
  1. 在App.js文件:
import { addToNum  }from ‘./store/modules/counterStore’;
<button onClick={()=>dispatch (addToNum(10))}> add to 10 </button>

十四、Redux与React-异步状态操作

  1. 创建store的写法保持不变,配置好同步修改状态的方法
  2. 单独封装一个函数,在函数内部return一个新函数,在新函数中
    • 封装异步请求获取数据
    •   调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch 提交
  3. 组件中dispatch 的写法保持不变

十五、Redux调试 - devtools

浏览器右上角三个点 -> 更多工具 -> 扩展程序 -> 安装Redux Devtools 3.0.19 -> 重启浏览器

十六、ReactRouter - 快速开始

1. 创建路由开发环境

使用路由我们还是采用CRA创建项目的方式进行基础环境配置

1.1创建React项目并安装所有依赖

npx create-react-app react-redux
npm i

1.2安装最新的ReactRouter包

npm i react-router-dom

1.3启动项目

npm run start

2. 配置基础路由步骤:

 安装路由包 **react-router-dom**
 准备两个**基础路由组件Layout和Login**
 在router/index.js文件中引入组件进行路由配置,**导出router实例********入口文件中渲染<RouterProvider />** ,传入router实例

3. ReactRouter - 抽象路由模块

// router/index.js文件下代码示例:
import Layout from  ‘../page/Layoutimport Login from  ‘../page/Loginimport { createBrowserRouter } from ‘react-router-dom’;
const router = createBrowserRouter([
    {
        path:‘/’,
        element:<Layout />
    },
    {
        path:‘/login’,
        element:<Login />
    },
])
export default router
// 入口文件index.js  
import React from ‘react’
import ReactDOM from ‘react-dom/client’
import ‘./index.scssimport { RouterProvider} from ‘react-router-dom’;
import router from ‘./router ’;
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
  <RouterProvider router ={router} />
)

4. 什么是路由导航

路由系统中多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

4.1声明式导航: 通过在模板中 组件描述出要跳转的地方

<Link to=”/article”>文章</Link>

语法说明:通过组件的to属性指定要跳转到路由path,组件被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可。

4.2编程式导航:通过‘ useNavigate ’钩子得到导航的方法,然后通过调用方法以命令式的形式进行路由跳转。

import { useNavigate } from ‘react-router-dom’;
const Login = ()=>{
  const navigate =  useNavigate();
  return(
    <div>登录页</div>
    <button onClick={()=>navigate(‘/article’)}>跳转文章页</button>
  )
}
export default Login

语法说明:通过调用navigate方法传入地址path实现跳转

4.3路由导航传参: 以编程式导航为例

import { useSearchParams, useParams } from ‘react-router-dom’;

searchParams传参:

传递参数:navigate(‘/article?id=100&name=jack’);

接收传参:const [params] = useSearchParams();

let id =  params.get(‘id’); // 100

params传参:

对应路由配置需修改为path:‘/actilcle/:id/:name’,

传递参数:navigate(‘/article/id=1001/name=jack’’);

接收传参:const params = useParams();

let id =  params.id;  let name=  params.name;

5.  嵌套路由

在一级路由又内嵌其他路由,这种关系就叫嵌套路由。

实现步骤:

1. 使用children属性匹配路由嵌套关系

2. 使用‘’组件配置二级路由渲染位置

// 代码示例: Layout 组件
import { useNavigate , Outlet } from ‘react-router-dom’;
const Layout = ()=>{
    const navigate =  useNavigate();
    return(
        <div>我是Layout </div>
        // 二级路由出口
        <Outlet />
        <button onClick={()=>navigate(‘/login’)}>跳转登录页</button>
        <button onClick={()=>navigate(‘/article’)}>跳转文章页</button>
    )
}
export default Layout
// 路由配置
{
    path:‘/’,
    element:‘Layoutchildren:[
        {
        path:‘/login’,
        element:</Login>
        },
        {
        path:‘/actilcle’,
        element:</Actilcle>
        }
    ]
}

3.  ReactRouter - 默认二级路由

当我们访问一级路由时,默认的二级路由组件看可以得到渲染,只需要二级路由的位置去掉path,设置index属性为true

// 代码示例:
{
    path:‘/’,
    element:‘Layoutchildren:[
        {
        index: true,
        element:</Login>
        },
        {
        path:‘/actilcle’,
        element:</Actilcle>
        }
    ]
}

4. ReactRouter - 404路由配置

场景:当浏览器输入url的路径在整个路由配置中找不到对应的path,为了用户体验,可以使用404兜底组件进行渲染。

404路由 - 实现步骤:

1. 准备一个NotFound组件

2. 在路由数组的末尾以*号作为路由path配置路由

// 代码示例: NotFound组件
const NotFound = ()=>{
  // 自定义模板
  return <div> this is notFound </div>
}
Export default NotFound
// 路由配置
{
  path:’*’,
  element:’NotFound’
}

  6.  ReactRouter - 两种路由模式

history模式和hash模式,ReactRouter分别由createBrowserRouter和createHashRouter函数负责创建。

history

url表现:url/login  底层原理:history对象+pushState事件 需要后端支持

hash

url表现:url/#/login  底层原理:监听hashChange事件  不需要后端支持

十七、项目中配置别名路径 @:

1. 路径解析配置(webpack),把 @/ 解析为src/借助craco插件

import Layout from ‘@/pages/Layout’;

配置步骤:

  •  安装craco插件   npm i -D @craco/craco
  •  项目根目录下创建配置文件craco.config
  •  在craco.config配置文件中添加路径解析配置,如下:
const path = require(‘path’);
module.exports = {
  webpack:{
    alias:{
      ‘@’: path.resolve(_dirname,‘src’)
    }
  }
}
  • 包文件中配置启动和打包命令:package.json
‘start’: ‘craco start’
‘build’: ‘craco build’

2. 路径联想配置(VScode) ,VScode在输入@/时,自动联想出对应的src/下的子级目录需要在项目根目录中新增一个jsconfig.json文件

配置步骤:

  •  根目录下新增配置文件 -  jsconfig.json
  •  添加路径提示配置,代码如下:
{
    “compilerOptions”:{
      “baseUrl”: “/”,
      “paths”: {
         “@/*”:[
           “src/*”
          ]
       }
    }
}

十八、json-server实现模拟数据Mock

json-server工具,它是一个node包,可以在不到30秒获得零编码的完整的Mock服务。

实现步骤:

  1. 项目中安装json-server工具:npm i -D json-server
  2. 准备一个json文件(在根目录新增server/data.json文件)
  3.  添加启动命令: “server”: “json-server ./server/data.json --port 8888”
  4.  访问接口进行测试: npm run server

十九、Ant Design Mobile 主题定制

定制方案****

1. 全局定制:整个应用范围内的组件都生效

// 实现方式:
:root:root {
  --adm-color-primary:#a062d4
}
  1. 局部定制:只在某些元素内部的组件生效
// 实现方式:
.purple {
  --adm-color-primary:#a062d4
}

二十、Ant Design 组件库

Ant Design 是由蚂蚁金服出品的社区使用最广的React PC端组件库内置了常用的现成组件,可以帮助我们快速开发PC管理后台项目。

// 安装Ant Design到项目
npm install antd  --save

//引入antd组件:
import { Button } from ‘antd’;
//使用:
<Button type= ‘text’>按钮<Button>

二十一、按照业务规范整理项目目录(重点是SRC目录)

  • 文件夹        作用
  • apis          接口
  • assets       静态资源
  • components    通用组件
  • pages         页面级组件
  • router        路由router
  • store         Redux状态
  • utils         工具函数

二十二、使用gitee管理项目

目的:为了记录每次阶段性的功能,采用git管理我们的项目,方便复习

实现步骤:

  1. 在gitee上初始化一个空项目仓库
  2. 把远程仓库和本地仓库关联
  3. 提交代码到远程仓库

二十三、封装request请求模块

在整个项目中会发送很多网络请求,使用axios第三方库统一封装,方便统一管理和复用

  1. 几乎所有的接口都是一样的接口域名
  2. 几乎所有的接口都需要设置一样的超时时间
  3. 几乎所有的接口都需要做Token权限处理 等等

axios 统一封装 -> axios实例 -> 接口1 = 接口2 = 接口3

// 在utils文件夹下新建一个request.js文件,代码示例:
// 根域名配置
// 超时时间
// 请求拦截器 / 相应拦截器
import axios from ‘axios’;
const baseUrl = process.env.VUE_APP_ENV
const request = axios.create({
  baseUrl,
  Timeout: 5000
})

// **添加请求拦截器**
// 在请求发送之前,做拦截插入一些自定义配置[参数的处理]
request.interceptors.request.use((config)=>{
         return config
    },(error)=>{
         return Promise.reject(error)
    }
)

// **添加相应拦截器**
// 在响应返回客户端之前做拦截,重点处理返回的数据
request.interceptors.response.use((response)=>{
     // 2xx 范围内的状态码都会触发该函数
     // 对响应的数据处理
     return response.data
    },(error)=>{
         return Promise.reject(error)
    }
)
export { request }

// 然后在utils文件夹下index.js文件**统一中转**
// 统一中转工具模块函数
// 如其他地方使用:import { request } from‘@/utils’
import { request } from‘./request’
export {
    request
}

登录-使用Redux管理token

token作为一个用户的标识数据,需要在很多个模块共享,Redux可以方便的解决状态共享问题。

Login组件体检 -> action -> Redux(Token管理)