React最新企业标准技术栈(React18+Redux+ ReactRouter +AntD),React+TypeScript(为大型项目奠定基础)

image

一、React介绍

React由Meta公司开发,是一个用于 构建Web和原生交互界面(IOS和Android上面的APP)的库

1、React的优势

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

(1)、组件化的开发方式

(2)、不错的性能

相较于其它前端框架(如VUE)的优势

(1)、丰富的生态

(2)、跨平台支持

React的市场情况

全球最流行,大厂必备

image

2、开发环境创建

create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用 执行命令:

npx create-react-app react-basic
  1. npx - Node.js工具命令,查找并执行后续的包命令

  2. create-react-app - 核心包(固定写法),用于创建React项目

  3. react-basic React项目的名称(可以自定义)

打开D:\project\react目录,cmd进入后输入命令:npx create-react-app react-basic,执行完成后如下所示:

image

此时react目录下就多出了一个react-basic目录

image

目录结构如下所示:

image

启动

cd react-basic
npm start

报错:

image

移除 Jest 相关的 ESLint 配置(推荐,最快捷)

如果你的项目目前不需要使用 Jest 进行测试,或者只是想先让项目跑起来,可以直接在 package.json 中删掉 react-app/jest 这一项。

将你的 eslintConfig 修改为:

"eslintConfig": {
  "extends": [
    "react-app"
  ] 
}

修改后重新启动

保留src目录下的index.js和App.js,其他文件全部删除,如下所示:

image

index.js是整个项目的入口,从这里开始运行

index.js中的内容如下:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

清理后如下所示:

// index.js是整个项目的入口,从这里开始运行
// react必要的两个核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
import App from './App';
// 把APP跟组件渲染到ip为root的dom节点上,root节点在public/index.html中
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

把项目的根组件App以react组件的方式渲染到public/index.html的root节点上

image

App.js内容如下:

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

清理后如下所示

function App() {
  return (
    <div className="App">
      this is APP
    </div>
  );
}

export default App;

App.js是项目的根组件,

此时页面效果如下:

image

 

二、JSX基础

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

const message = 'this is message'
function App() {
  return (
    <div className="App">
      <h1>this is title</h1>
      {message}
    </div>
  );
}

export default App;

优势:

  1. HTML的声明式模版写法

  2. JavaScript的可编程能力

JSX的本质

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

image

JSX高频场景-JS表达式

在JSX中可以通过 大括号语法{} 识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

(1)、使用引号传递字符串

(2)、使用JS变量

(3)、函数调用和方法调用

  1. 使用JavaScript对象

注意:if语句、switch语句、变量声明不属于表达式,不能出现在{}中

const message = 'this is message'
function getAge(){
  return 18
}
function App() {
  return (
    <div className="App">
      <h1>this is title</h1>
      {/* 字符串识别 */}
      {'this is str'}
      {/* 变量识别 */}
      {message}
      {/* 变量识别 */}
      {message}
      {/* 函数调用 渲染为函数的返回值 */}
      {getAge()}
       {/* 方法调用 */}
       {new Date().getDate()}
       {/* 使用js对象 */}
       <div style={{color: 'red'}}>this is div</div>
    </div>
  );
}

export default App;

效果:

image

JSX高频场景-列表渲染

image

在JSX中可以使用原生js中的map方法 实现列表渲染

const list = [
  {id:1001, name:'Vue'},
  {id:1002, name: 'React'},
  {id:1003, name: 'Angular'}
]
function App() {
  return (
    <ul>
      {list.map(item=><li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

export default App;

效果如下:

image

JSX高频场景-条件渲染

image

在React中,可以通过逻辑与运算符&&、三元表达式(?:) 实现基础的条件渲染

const flag = true
const loading = false
function App() {
  return (
    <>
      {flag && <span>this is span</span>}
      {loading ? <span>loading...</span>:<span>this is span</span>}
    </>
  );
}

export default App;

效果:this is spanthis is span

JSX高频场景-复杂条件渲染

需求:列表中需要根据文章的状态适配

解决方案:自定义函数 + 判断语句

const type = 1  // 0|1|3

function getArticleJSX(){
  if(type === 0){
    return <div>无图模式模版</div>
  }else if(type === 1){
    return <div>单图模式模版</div>
  }else if(type === 3){
    return <div>三图模式模版</div>
  }
}
function App() {
  return (
    <>
      { getArticleJSX() }
    </>
  );
}

export default App;

效果:单图模式模版

三、React的事件绑定

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

image

使用事件参数

在事件回调函数中设置形参e即可

function App() {
  const clickHandler = (e)=>{
    console.log('button按钮点击了', e)
  }
  return (
    <button onClick={clickHandler}>click me</button>
  );
}

export default App;

效果:

image

传递自定义参数

function App() {
  const clickHandler = (name)=>{
    console.log('button按钮点击了', name)
  }
  return (
    <button onClick={()=>clickHandler('jack')}>click me</button>
  );
}

export default App;

效果:

image

注意:不能直接写函数调用,这里事件绑定需要一个函数引用

同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应

function App() {
  const clickHandler = (name,e)=>{
    console.log('button按钮点击了', name,e)
  }
  return (
    <button onClick={(e)=>clickHandler('jack',e)}>click me</button>
  );
}

export default App;

效果:

image

四、React组件基础使用

组件是什么?

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

image

组件基础使用

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

// 1. 定义组件
function Button(){
  return <button>click me</button>
}
function App() {
  return (
    <div>
      {/* 自闭和 */}
      <Button/>
      {/* 成对标签 */}
      <Button></Button>
    </div>
  );
}

export default App;

效果:

image

组件状态管理-useState

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

image

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  // 1. 调用useState添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [ count, setCount ] = useState(0) // 初始值为0,返回一个数组,

  return (
    <div>
      <button onClick={()=>setCount(count+1)}>{ count }</button>
    </div>
  );
}

export default App;

image

 另一种写法

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  // 1. 调用useState添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [ count, setCount ] = useState(0)
    // 2. 点击事件回调
  const handleClick = () => {
    // 作用: 
    // 1. 用传入的新值修改count
    // 2. 重新使用新的count渲染UI
    setCount(count + 1)
  }
  return (
    <div>
      <button onClick={handleClick}>{ count }</button>
    </div>
  );
}

export default App;

状态的修改规则

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

 下面的代码是修改count的值,不会引发视图更新

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  // 1. 调用useState添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  let [ count, setCount ] = useState(0)
    // 2. 点击事件回调
  const handleClick = () => {
    // 作用: 
    // 1. 用传入的新值修改count
    // 2. 重新使用新的count渲染UI
    count++
    console.log(count)
  }
  return (
    <div>
      <button onClick={handleClick}>{ count }</button>
    </div>
  );
}

export default App;

下面的方法使用setCount方法,用传入的新值count+1修改count,重新使用新的count渲染UI

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  // 1. 调用useState添加一个状态变量
  // count 状态变量
  // setCount 修改状态变量的方法
  const [ count, setCount ] = useState(0)
    // 2. 点击事件回调
  const handleClick = () => {
    // 作用: 
    // 1. 用传入的新值修改count
    // 2. 重新使用新的count渲染UI
    setCount(count + 1)
    console.log(count)
  }
  return (
    <div>
      <button onClick={handleClick}>{ count }</button>
    </div>
  );
}

export default App;

 效果:

image

修改对象状态

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

下面的代码给useState传递一个对象,对象的属性为name,如果在回调函数中直接通过form.name进行修改是不会引发视图的变化

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  const [ form, setForm ] = useState({name: 'jack'})
    // 2. 点击事件回调
  const handleChangeName = () => {
    form.name = 'john'
  }
  return (
    <div>
      <button onClick={handleChangeName}>{ form.name }</button>
    </div>
  );
}

export default App;

下面的代码在回调函数中调用set方法,传过来一个全新的对象,

// useState实现一个计数器按钮
import { useState } from 'react'
function App() {
  const [ form, setForm ] = useState({name: 'jack'})
    // 2. 点击事件回调
  const handleChangeName = () => {
    setForm({
      ...form,
      name: 'john'
    })
  }
  return (
    <div>
      <button onClick={handleChangeName}>{ form.name }</button>
    </div>
  );
}

export default App;

效果如下:

image

组件的基础样式处理

React组件基础的样式控制有俩种方式,行内样式和class类名控制

方式一:行内样式

<div style={{ color:'red'}}>this is div</div>

完整代码如下:

import './index.css'
function App() {
  return (
    <div>
      <div style={{ color:'red'}}>this is div</div>
    </div>
  );
}

export default App;

方式二:class类名

新建index.css文件,内容如下:

.foo{
  color: red;
}

完整代码如下:

import './index.css'
function App() {
  return (
    <div>
      <span className="foo">this is span</span>
    </div>
  );
}

export default App;

效果如下:

image

React表单控制

受控绑定

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

image

react的状态:通过useState生成状态值,这个状态值的变化会引起视图更新,

input框的状态:通过e.target.value可以拿到当前用户在input输入框里面输入的最新数据,

左边的react状态来控制右边input框的状态,input框到底显示什么由state来决定,而且input框里面输入的最新数据和state是保持同步的。

import { useState } from 'react'
function App(){
  const [value, setValue] = useState('')
  return (
    <input 
      type="text" 
      value={value} 
      onChange={e => setValue(e.target.value)}
    />
  )
}
export default App

当用户输入最新数据的时候,state中的数据相应变化,当state中的值改变,input输入框的值也跟着改变。

image

非受控绑定

概念:通过获取DOM的方式获取表单的输入数据

使用useRef创建ref对象,

const inputRef = useRef(null)  // 初始值为null,

ref对象与JSX绑定

<input type="text" ref={inputRef}/>

在DOM可用时,通过inputRef.current拿到DOM对象,

console.log(inputRef.current)

获取dom元素

import React, { useRef } from 'react';
function App(){
  const inputRef = useRef(null)

  const showDom = ()=>{
    console.log(inputRef.current)
  }
  return (
    <div>
      <input type="text" ref={inputRef}/>
      <button onClick={showDom}>获取dom</button>
    </div>
    
  )
}
export default App

效果如下:

image

获取dom属性:

import React, { useRef } from 'react';
function App(){
  const inputRef = useRef(null) 

  const onChange = ()=>{
    console.log(inputRef.current.value)
  }
  return (
    <div>
      <input 
      type="text" 
      ref={inputRef}
      onChange={onChange}
    />
    </div>
    
  )
}
export default App

效果如下:

image

import React, { useRef } from 'react';
function App(){
  const inputRef = useRef(null)

  const showDom = ()=>{
    console.log(inputRef.current.value)
  }
  return (
    <div>
      <input type="text" ref={inputRef}/>
      <button onClick={showDom}>获取dom</button>
    </div>
    
  )
}
export default App

效果:

image

五、React组件通信

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

image

  1. A-B 父子通信

  2. B-C 兄弟通信

  3. A-E 跨层通信

父子通信-父传子

image

实现步骤

(1)、父组件传递数据 - 在子组件标签上绑定属性

<Son name={name}/>

(2)、子组件接收数据 - 子组件通过props参数接收数据

function Son(props){
  return <div>{ props.name }</div>
}

完整代码如下:

function Son(props){
  return <div>{ props.name }</div>
}
function App(){
  const name = 'this is app name'
  return (
    <div>
       <Son name={name}/>
    </div>
    
  )
}
export default App

props说明:props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX

image

props是只读对象 子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

特殊的prop-chilren

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

image

父子通信-子传父

image

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

function Son({ onGetMsg }){
  const sonMsg = 'this is son msg'
  return (
    <div>
      {/* 在子组件中执行父组件传递过来的函数 */}
      <button onClick={()=>onGetMsg(sonMsg)}>send</button>
    </div>
  )
}
function App(){
  const getMsg = (msg)=>console.log(msg)
  return (
    <div>
      {/* 传递父组件中的函数到子组件 */}
       <Son onGetMsg={ getMsg }/>
    </div>
    
  )
}
export default App

效果:

image

兄弟组件通信

image

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

  1. A组件先通过子传父的方式把数据传递给父组件App

  2. App拿到数据之后通过父传子的方式再传递给B组件

// 1. 通过子传父 A -> App
// 2. 通过父传子 App -> B
import { useState } from "react"

function A ({ onGetAName }) {
  // Son组件中的数据
  const name = 'this is A name'
  return (
    <div>
      this is A compnent,
      <button onClick={() => onGetAName(name)}>send</button>
    </div>
  )
}
function B ({ name }) {
  return (
    <div>
      this is B compnent,
      {name}
    </div>
  )
}

function App(){
  const [name, setName] = useState('')
  const getAName = (name) => {
    setName(name)
  }
  return (
    <div>
      this is App
      <A onGetAName={getAName} />
      <B name={name} />
    </div>
    
  )
}
export default App

效果如下:

image

跨层组件通信

image

实现步骤:

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

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

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

// App -> A -> B

import { createContext, useContext } from "react"

// 1. createContext方法创建一个上下文对象

const MsgContext = createContext()

function A () {
  return (
    <div>
      this is A component
      <B />
    </div>
  )
}

function B () {
  // 3. 在底层组件 通过useContext钩子函数使用数据
  const msg = useContext(MsgContext)
  return (
    <div>
      this is B compnent,{msg}
    </div>
  )
}

function App () {
  const msg = 'this is app msg'
  return (
    <div>
      {/* 2. 在顶层组件 通过Provider组件提供数据 */}
      <MsgContext.Provider value={msg}>
        this is App
        <A />
      </MsgContext.Provider>
    </div>
  )
}

export default App

效果:

image

六、React副作用管理-useEffect

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

image

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

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

语法:

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

说明:

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

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

import { useEffect, useState } from "react"

function App () {
  // 创建一个状态数据
  const [list,setList] = useState([])
  useEffect(() => {
    const URL = 'http://geek.itheima.net/v1_0/channels'
    // 实现副作用操作逻辑-获取频道列表
    async function getList(){
      const res = await fetch(URL)
      const jsonRes = await res.json()
      console.log(jsonRes)
      setList(jsonRes.data.channels)
    }
    getList()
  },[])
  return (
    <div>
      this is app
      <ul>
        {list.map(item=><li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  )
}

export default App

效果:

image

useEffect依赖说明

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

image

(1)、没有依赖项的情况,副作用函数的执行时机是:组件初始渲染和组件更新时执行

import { useEffect, useState } from "react"

function App () {
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log("副作用函数执行了!")
  })
  return (
    <div>
      this is app
      <button onClick={()=>setCount(count+1)}>+{count}</button>
    </div>
  )
}

export default App

效果:

image

(2)、空数组依赖,只在初始渲染时执行一次

import { useEffect, useState } from "react"

function App () {
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log("副作用函数执行了!")
  },[])
  return (
    <div>
      this is app
      <button onClick={()=>setCount(count+1)}>+{count}</button>
    </div>
  )
}

export default App

效果:

image

 (3)、传入特定依赖项,组件初始渲染和依赖项变化时执行

import { useEffect, useState } from "react"

function App () {
  const [count, setCount] = useState(0)
  useEffect(() => {
    console.log("副作用函数执行了!")
  },[count])
  return (
    <div>
      this is app
      <button onClick={()=>setCount(count+1)}>+{count}</button>
    </div>
  )
}

export default App

 效果:

image

清除副作用

概念:在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉(定时器如果不及时清理会产生内存泄漏),这个过程就是清理副作用

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

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

import { useEffect, useState } from "react"

function Son () {
  // 1. 渲染时开启一个定时器
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('定时器执行中...')
    }, 1000) // 每隔一秒钟

    return () => {
      // 清除副作用(组件卸载时)
      clearInterval(timer)
    }
  }, []) // 空数组:只在渲染时执行一次
  return <div>this is son</div>
}

function App () {
  // 通过条件渲染模拟组件卸载
  const [show, setShow] = useState(true)
  return (
    <div>
      {show && <Son />}
      <button onClick={() => setShow(false)}>卸载Son组件</button>
    </div>
  )
}

export default App

如果在清除副作用的函数中不清除定时器,那么即使卸载了Son组件,定时器依然在执行。

效果如下:我们发现:卸载Son组件后,定时器不再执行。

image

七、自定义Hook实现

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

image

需求:点击toggle按钮控制“this is div”的显示和隐藏

(1)、不封装直接实现

import { useState } from "react"

function App () {
  const [ value, setValue ] = useState(true)
  const toggle = ()=> setValue(!value)
  return (
    <div>
      {value && <div>this is div</div>}
      <button onClick={toggle}>toggle</button>
    </div>
  )
}

export default App

如果有一天在其他地方控制的不是div,而是其他元素,还是需要把下面的两行代码再写一遍,即不可以复用。

布尔切换的逻辑是和当前组件耦合在一起的,不能在其他组件实现复用

const [ value, setValue ] = useState(true)
const toggle = ()=> setValue(!value)

那有没有一种办法可以实现不管在哪个地方想要切换显示和隐藏的逻辑,都可以快速的复用过去

解决思路:自定义Hook

// 封装自定义Hook
import { useState } from "react"

function useToggle () {
  // 可复用的逻辑代码
  const [value, setValue] = useState(true)
  const toggle = () => setValue(!value)
  // 哪些状态和回调函数需要在其他组件中使用 return
  return {
    value,
    toggle
  }
}

// 封装自定义hook通用思路

// 1. 声明一个以use打头的函数
// 2. 在函数体内封装可复用的逻辑(只要是可复用的逻辑)
// 3. 把组件中用到的状态或者回调return出去(以对象或者数组)
// 4. 在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
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、其它的函数中

(1)、在外部调用是不被允许的,如下所示

image

 (2)、把Hook函数的执行放到if条件语句中是不被允许的,如下所示

image

 

posted on 2026-09-17 11:19  周文豪  阅读(5)  评论(0)    收藏  举报