react是什么?
React 是一个开源的 JavaScript 库,由Facebook开发,主要用于构建用户界面,特别是复杂的单页应用程序(SPA)。它专注于视图层,即用户界面的开发,可以与多种库或框架结合使用,以管理应用程序的状态和数据流动。
React 的特点:
- 虚拟 DOM:
- React 引入了虚拟 DOM 的概念,这是一种内存中的轻量级数据结构,表示实际 DOM 的抽象。当应用状态改变时,React 会高效地计算出虚拟 DOM 的最小变更量,并将这些变更应用到实际 DOM 上,从而大幅提升了性能。
- 组件化开发:
- React 鼓励采用组件化的方式来构建 UI,即将 UI 分解为可复用的独立组件。每个组件负责管理自己的状态(state)和属性(props),这样可以提高代码的模块化程度,易于维护和重用。
- 单向数据流:
- React 推崇一种单向数据流的架构,数据通常自上而下地传递给组件树,这简化了状态管理和问题追踪,减少了数据流动的复杂性。
- JSX:
- JSX 是一种语法扩展,允许在 JavaScript 中混写 HTML 样式的代码。这使得描述 UI 结构变得更加直观和简洁,同时也方便静态代码分析工具进行检查。
- 服务器端渲染:
- React 支持服务器端渲染(SSR),这对于改善首屏加载速度和搜索引擎优化(SEO)非常有利。
React 的优势:
- 性能优化:通过虚拟 DOM 减少了直接操作实际 DOM 的次数,提升了应用运行效率。
- 易学易用:React 的学习曲线相对较平缓,开发者可以快速上手并开始开发。
- 强大的生态系统:React 拥有庞大的社区支持和丰富的第三方库,如 Redux、React Router 等,可以满足各种开发需求。
- 代码复用与模块化:组件化的开发方式促进了代码的复用,使得开发大型项目更加高效。
- 兼容性****和灵活性:React 不限定于特定的技术栈,可以与多种后端技术和前端库集成,提供了高度的灵活性。
- React Native:React 的理念还延伸到了原生移动应用开发,React Native 允许使用相同的开发模式来构建原生移动应用,实现了跨平台开发能力。
这些特点和优势使得 React 成为了现代Web开发中极为流行的工具之一。
初始化一个react项目
输入以下命令来创建一个新的React项目,其中react-demo
替换为你想要的项目名称:
1
| npx creact-react-app react-demo
|
使用 npx 命令会自动安装React及相关依赖,并设置好项目的基本结构
cd 切换到创建的文件夹
使用 npm starrt
启动项目
项目的目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| react-demo ├─ node_modules ├─ public ├─ favicon.ico ├─ index.html ├─ logo192.png ├─ logo512.png ├─ manifest.json ├─ robots.txt ├─ src ├─ App.css ├─ App.js ├─ App.test.js ├─ index.css ├─ index.js ├─ logo.svg ├─ reportWebVitals.js ├─ setupTests.js ├─ package.json
|
下边我们分别来说一下每个文件(夹)的作用:
Node_modules
node的包目录,项目所依赖到的所有第三方包,没啥可说的
Public
public用来存放首页模板及静态资源,该目录中除了index.html
都可以删除
- index.html 首页模板(不能删)
- favicon.ico 收藏夹图标(可以删,开发中会替换为自己的图标)
- logoxxx.png React的Logo(删)
- manifest.json(PWA的配置文件,大部分情况没啥用,删)
- robots.txt(搜索引擎配置文件,可删)
Src
源码目录,我们写代码就是在这里
index.js
项目入口文件,不能删。
index.css
index.js的样式表,可改可删
App.js
主组件,可改可删
App.css
App.js的样式表,可改可删
xxx.test.js
带有test的都是单元测试的文件,可删
reportWebVitals.js
应用性能统计相关的代码,简单说用户在使用应用时,该模块可以统计用户加载应用所花费的时间,并且根据需要发送给统计服务器,如果没有相关需求可删。
编写我们的第一个react应用,浅尝一下
1 2 3 4 5 6 7 8 9 10 11 12 13
| function App() { return <div className="App">你好,这是我的第一个react项目!</div>; }
export default App; import { createRoot } from "react-dom/client";
import App from "./App.tsx";
createRoot(document.querySelector("#root")).render(<App />);
|
createRoot()
createRoot(domNode, options?)
domNode
:一个 ,DOM 元素React 将为这个 DOM 元素创建一个根节点然后允许你在这个根节点上调用函数
options?
:可选的根节点配置对象
返回值
createRoot
返回一个带有两个方法的的对象,这两个方法是:render
和 unmount
unmount()
调用 unmount
以销毁 React 根节点中的一个已经渲染的树
返回值
返回 undefined
render()
用来将React元素渲染到根元素中
首次调用 root.render
时,React 会先清空根节点中所有已经存在的 HTML,然后才会渲染 React 组件
重复调用会在内部进行diff算法,将两次渲染结果进行比较,只修改发生变化的部分,避免没必要的更新,提高性能
1 2 3 4 5 6 7
| react17 写法,不建议使用,后续可能会不再维护 import React from "react"; import ReactDOM from "react-dom"; import App from "./App.jsx";
ReactDOM.render(<App />, document.querySelector("#root"));
|
在react的核心包中createRoot
方法创建根节点,还有一个方法createElement
创建react元素,且创建后无法修改,只能覆盖
1 2
| import { createElement } from 'react-dom/client' createElemnet('div',{id:'box',type:'div',className:'btn',onClick:()=>{alert('哎呀!你干嘛.')}},'你好!我是蔡徐坤')
|
接收三个参数,第一个是创建元素的类型,第二个是元素对应的一些样式的配置对象,第三个参数是要填充的内容
元素名称必须为html格式的小写
标签中的属性class属性使用className,设置事件属性名采用驼峰命名
元素的内容,子元素,直接在后面使用逗号隔开添加
jsx概念
JSX是JavaScript XML的缩写,它是一种用于在React中编写UI组件的语法扩展。JSX允许开发者在JavaScript代码中编写类似HTML的结构,使得编写和阅读React组件更加直观和简洁。虽然它看起来像是在JavaScript中直接写HTML,但实际上,JSX被编译成普通的JavaScript函数调用,这些函数调用会创建虚拟DOM元素树,最终渲染为真实的DOM元素。
jsx本质
JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行
jsx是声明式编程,简单理解就是以结果为导向,就像是你的老板,我不管你的过程我只要结果
1 2 3 4 5 6 7
|
const button = createElement('button',{},'按钮')
const button = <button>我是按钮</button>
|
jsx中使用js表达式
在jsx中可以通过 大括号语法{ }
识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> const app = <div calssName="app"> // 使用引号传递字符串 {'php是世界上最好的语言'} // 识别变量 const name = tom { name } // 函数调用 const getUserInfo = () => { return { name: Tom , age: 18 } } { getUserInfo() } // 方法调用 { New Date().getDate() } //使用js对象 <div style={{color:'red'}}>Hello World</div> // 外层{ }识别表达式语法,内层{ }为对象 </div> </script>
|
注意:
- jsx不是字符串,不要加引号
- jsx中的html标签小写开头,React组件为大写开头
- jsx有且只有一个根标签,必须正确结束
- 布尔类型、Null 、直接写对象的形式以及 Undefined 将会忽略
- if语句、Switch语句、变量声明属于语句,不是表达式,不能出现在{ }中
jsx
只允许只有一个根标签,我们也可以使用 的虚拟根标签来包裹内容,最终是不会渲染出来的,可以理解为vue中的template
标签
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Fragment } from "react/jsx-runtime";
export default function App() { return ( <Fragment> <h1>Hello, world!</h1> </Fragment> ) }
简写形式 export default function App() { return ( <> <h1>Hello, world!</h1> </> ) }
|
上面提到了一些像布尔类型,null和undefined是不会显示的,那如果我们在调试的时候需要显示那该怎么办呢?
通过值 加上空字符串的形式来解决,对象我们可以使用JSON.stringify()转成字符串的形式来显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export default function App() { return ( <> <div> { false + ''} <br /> { true + ''} <br /> { undefined + ''} <br /> {JSON.stringify({name: 'tom', age: 30})} <br /> </div> </> ); }
|
列表渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| const heroList = [ { id: 1, name: '锐雯', lane: '上单', },
{ id: 2, name: '泰拉米尔', lane: '上单', },
{ id: 3, name: '奎因', lane: '上单', },
{ id: 4, name: '奥恩', lane: '上单', },
{ id: 5, name: '亚索', lane: '中单', },
{ id: 6, name: '李青', lane: '打野', },
{ id: 7, name: '努努', lane: '打野', },
{ id: 8, name: '艾希', lane: 'ADC', },
{ id: 9, name: '薇恩', lane: 'ADC', },
{ id: 10, name: '卡特琳', lane: '辅助', },
{ id: 11, name: '莫德凯撒', lane: '辅助', }, ]
function getHeroList() { return ( <ul> {heroList.map(hero => ( <li key={hero.id}>{hero.name} 分路:{hero.lane}</li> ))} </ul> ) }
export default getHeroList;
|
条件渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export const isLogin = false
export selectedValue = 1
export const getOptions = (val)=>{ if (val === 1) { return <div>选项1</div> }else if (val === 2) { return <div>选项2</div> }else { return <div>选项3</div> } }
function App() { return ( <div>{ isLogin ? '登录成功' : '未登录' }</div> <div>{ isLogin && '登录成功!' }</div> <div>{ getOptions }</div> ) }
|
响应事件
1 2 3 4 5 6
| function myButton () { const handleClick = () => alert('喜中500万,请到缅北kk园区兑换!') return ( <button onClick={ handleClick }>开奖</button> ) }
|
onClick={handleClick}
的结尾没有小括号!不要调用事件处理函数:你只需把函数传递给事件即可。当用户点击按钮时 React 会调用你传递的事件处理函数。
传递参数
通过一个箭头函数的回调来传值
1 2 3 4 5 6 7 8 9 10 11
| export default function App() { const handleClick = (e: any, value: object) => { console.log(e, value); };
return ( <> <button onClick={(e) => handleClick(e, {name: '123', age: 123})}>点击按钮</button> </> ); }
|
组件点标记写法
有两种方式,一种是对象的方式,一种是函数的形式
函数的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const App = () => { return ( <> <div>hello</div> <User/> </> ) }
const User = () => { return ( <div>Welcome</div> ) }
export default App;
|
对象的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const App = () => { return ( <> <div>hello</div> <Abc.User/> <Abc.Info/> </> ) }
const Abc = { User() { return <div>Welcome</div> }, Info() { return <div>Info</div> }, }
|
这种好处就是我们可以在一个单独的模块下维护多个属于这一个类型的组件,在使用时就知道这个组件是属于哪一个大模块下的,方便维护
同样你也可以选择使用解构,如果你愿意的话
组件通讯
父向子通讯
react中不支持子向父传值
传递给子组件的数据是只读的,不允许进行修改
传递字符串,变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const App = () => { const text = '你好 React!' return ( <> <div> 父组件 <User name='张三' text={text} /> // 在父组件中使用属性向子组件传值 </div> </> ) }
|
在子组件中可以使用 porps 进行接收或是对其进行解构渲染,结果是以对象的形式进行包裹
1 2 3 4 5 6 7 8 9 10
| const User = ({ name, text }) => { return ( <> <div>子组件:user界面</div> <div>{name}</div> <div>{text}</div> </> ) } export default App
|
传递函数,事件
事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const App = () => { const getMsg = () => { alert('调用了父组件的方法') } return ( <div> <h1>父组件</h1> <Child onClick={getMsg} /> // 这里的 onClick 相当于就是一个属性用来传递数据的,所以在点击时并不会触发点击事件 </div> ) }
const Child = ({ onClick }) => { return ( <div> <h1>子组件</h1> <button onClick={onClick}>点击</button> </div> ) }
export default App
|
自定义函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const App = () => { const getData = data => { console.log(data) } return ( <div> <h1>父组件</h1> <Child getData={getData} /> // 向子组件传递了一个自定义函数 </div> ) }
const Child = ({ getData }) => { const text = '来自遥远的子组件的数据' return ( <div> <h1>子组件</h1> <button onClick={getData(text)}>点击</button> // 在子组件中点击触发这个自定义函数,并传递数据 </div> ) }
export default App
|
使用扩展运算符批量传递数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const App = () => { const data = { name: '张三', age: 18, gender: '男', address: '北京市', hobby: ['篮球', '足球', '游泳'], } return ( <div> <h1>父组件</h1> <Child {...data} /> </div> ) }
const Child = ({ name, age, gender, address, hobby }) => { return ( <div> <h1>子组件</h1> <h3>{name}</h3> <h3>{age}</h3> <h3>{gender}</h3> <h3>{address}</h3> <ul> {hobby.map((item, index) => { return <li key={index}>{item}</li> })} </ul> </div> ) }
export default App
|
特殊的children属性
当内容嵌套在子组件中的标签中时,父组件会在children属性中进行接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const Son = ({children}) => { return( <div>{children}</div> ) }
const App = () => { return ( <> <Son> <div>子组件中的数据</div> </Son> </> ) }
export default App
|
子向父通讯
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { useState } from 'react' import Son from './Son'
const App = () => { const [msg, setMsg] = useState('hello') const getMsg = msg => { setMsg(msg) } return ( <> <div> <span>{msg}</span> <Son onGetMsg={getMsg} /> </div> </> ) }
export default App
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const Son = ({onGetMsg}) => { const msgText: String = 'hello world' return ( <> <div> this is Son: <button onClick={()=>onGetMsg(msgText)}>send</button> </div> </> ) }
export default Son
|
兄弟组件通讯(也就是下面案例中的组件共享数据)
借助状态提升,通过数据下放,实现兄弟组件之间的数据通讯
在a页面点击发送数据到父组件,再由父组件传递数据给b页面实现数据传递
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useState } from 'react' import A from './A' import B from './B'
const App = () => { const [msg, setMsg] = useState("")
const getMsg = (text)=> { setMsg(text) }
return ( <> <A onGetMsg={getMsg} /> <B msg={msg}/> </> ) }
export default App
|
a页面
1 2 3 4 5 6 7 8 9 10 11
| const A = ({onGetMsg})=> { const aText = 'this is a text' return ( <> <div>page a</div> <button onClick={()=> onGetMsg(aText)}>send</button> </> ) }
export default A;
|
b 页面
1 2 3 4 5 6 7 8 9 10
| const B = ({msg})=> { return ( <> <div>page b</div> <div>a 页面的数据:{msg}</div> </> ) }
export default B;
|
传递设置默认值
1 2 3 4 5 6 7 8 9 10 11 12 13
| const App = () => { const number = 100 const data = 'App组件数据内容' return ( <div> <h1>父组件</h1> <Child number={number} data={data} /> </div> ) }
|
方式一:使用 es6 添加默认值的方式,直接在后面赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const Child = ({ number = 0, data = '默认显示的数据' }) => { return ( <div> <h2>子组件</h2> {number} <hr /> {data} </div> ) }
const Child = ({ number, data }) => { return ( <div> <h2>子组件</h2> {number} <hr /> {data} </div> ) }
|
方式二: 使用 react 提供的defaultProps 属性,在组件中添加默认值
1 2 3 4 5
| Child.defaultProps = { number: 0, data: '默认显示的数据', } export default App
|
常用的 react hook
useState
在 vue3
中我们使用 ref
和 reactive
来声明响应式数据
而在 react
中我们使用 useState
钩子,来创建一个响应式数据,通过 count 来访,通过 setCount 来修改数据
1 2 3 4 5 6 7 8 9 10 11 12
| import { useState } from 'react'
function App() { const [count, setCount] = useState(0)
return ( <div> <div>{count}</div> <button onClick={() => setCount(count + 1)}>按钮</button> </div> ) }
|
useMemo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useMemo, useState } from 'react' import './App.css'
const App = () => { const [count, setCount] = useState(1)
const double = useMemo(() => { return count * 2 }, [count]) return ( <> <button onClick={() => setCount(count + 1)}>double</button> <div className='fs20'>{double}</div> </> ) }
export default App
|
useEffect
useEffect
在函数组件中执行副作用操作,如数据获取、订阅或手动更改 DOM。它可以看作是 componentDidMount
, componentDidUpdate
, 和 componentWillUnmount
的组合
简单理解就是 vue 中的 onMounted 周期
1 2 3
| useEffect(()=>{ // 需要执行的操作 },[])
|
- 参数一是一个函数,可以把它叫做副作用函数,在里面放入需要执行的操作
- 参数二是一个依赖项数组,可选,不同的依赖项会影响第一个参数函数的执行
依赖项 |
函数执行时机 |
不传 |
组件初始化渲染,组件更新时会再次执行 |
空数组 |
只会在初始化渲染执行一次 |
添加特定依赖项 |
组件初始化渲染,特定依赖项更新时执行 |
useEffect清除副作用
在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
可以理解为 vue 生命周期中的 beforedestory
只需要在实现副作用逻辑的操作里面后面 return 一个函数里面写上清除副作用的逻辑就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| useEffect(()=>{
return ()=> {
} },[]) import React, { useState, useEffect } from 'react';
function MyComponent() { const [count, setCount] = useState(0);
useEffect(() => { const timerID = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000);
return () => { clearInterval(timerID); }; }, []);
return ( <div> <h1>{count}</h1> </div> ); }
export default MyComponent;
|
useContext
useReducer
和useState类似用于管理相对复杂的状态数据
- 定义 reducer 函数,依据 action 对象的不同状态返回对应的 state
- 调用 useReducer hook函数,传入定义好的 reducer 函数和默认初始状态
- 使用时调用dispatch函数传入一个 action 对象,对象中包含一个 type 属性,reducer 函数会依据这个 type 属性进行不同的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { useReducer } from 'react'
function reducer(state,{type}) { if(type === 'inc') { return state + 1 } else if(type === 'dec') { return state - 1 } else { return state } }
function App() { const [state,dispatch] = useReducer(reducer,0)
return ( <> <button onClick={() => dispatch({type: 'dec'})}>-</button> <span>{state}</span> <button onClick={() => dispatch({type: 'inc'})}>+</button> </> ) } export default App
|
useCallback
useCallback
返回一个 memoized 回调函数,避免在每次渲染时重新创建函数,适用于传递给子组件的回调函数,以优化性能
- 在需要缓存函数的地方调用
useCallBack
,传入需要缓存的函数及依赖项数组
- 当数组中的值变化时,重新才会创建回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { useState, useCallback } from 'react';
function Parent() { const [count, setCount] = useState(0);
const increment = useCallback(() => { setCount(count + 1); }, [count]);
return <Child onClick={increment} />; }
function Child({ onClick }) { return <button onClick={onClick}>增加</button>; }
export default Parent;
|
useRef
用于绑定dom对象,访问组件的实例,类似于vue中的ref
使用时使用 .current 属性进行访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { useRef } from 'react';
function TextInputWithFocusButton() { const inputEl = useRef(null);
const onButtonClick = () => { inputEl.current.focus(); };
return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
export default TextInputWithFocusButton;
|
forwardRef
使用 ref 暴露 dom 节点给父组件
在vue中是需要使用defineexpose来暴露节点,然后通过组件的ref来获取想要的信息
父组件
1 2 3 4 5 6 7 8 9 10 11
| function App() { const inputRef = useRef(null) return ( <> <Son ref={inputRef} /> <button onClick={()=>inputRef.current.focus()}>获取焦点</button> //通过.current获取到组件实例来进行相应的操作 </> ) }
export default App
|
子组件
1 2 3 4
| const Son = forwardRef((props,ref)=> { return <input ref={ref} /> })
|
uselmperativeHandle
通过ref暴露子组件的方法
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13
| function App() { const inputRef = useRef(null) const focusHandler = () => inputRef.current.focusIpt() return ( <> <Son ref={inputRef} /> <button onClick={focusHandler}>获取焦点</button> </> ) }
export default App
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Son = forwardRef((props,ref)=> {
const iptRef = useRef(null) const focusIpt = () => iptRef.current.focus()
useImperativeHandle(ref,()=> { return { focusIpt } }) return <input ref={iptRef} /> })
|
组件间共享数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { useState } from 'react';
export default function MyApp() { const [count, setCount] = useState(0);
function handleClick() { setCount(count + 1); }
return ( <div> <h1>数据更新</h1> {/* 将 handleClick 函数和 count 状态通过 props 传递给 MyButton 组件 */} <MyButton onClick={ handleClick } count={ count }/> <br/> <MyButton onClick={ handleClick } count={ count }/> </div> ); }
function MyButton({ onClick, count }) { return ( <button onClick={ onClick }> {/* 显示从父组件传递来的 count 值 */} 当前数字为:{ count } </button> ); }
|
- 我们将
count
状态和 handleClick
事件处理函数从 MyButton
组件移动到了 MyApp
组件中。这种做法称为“状态提升”,可以将共享的状态和行为放在更高层级的组件中。
- 在
MyApp
组件中,我们通过 props 将 handleClick
函数和 count
状态传递给每个 MyButton
组件。这样,每个按钮都可以访问相同的状态和行为。
- 在
MyButton
组件中,我们接收 onClick
和 count
作为 props,并将 onClick
函数绑定到按钮的点击事件上。每个按钮显示的数字都是从父组件传递过来的 count
值。
组件的排列组合
一个父组件里面套了一个子组件,那如果我想在子组件里面接着嵌套其他的功能模块的时候是把它放到子组件的内容里面吗?
我们来 try 一 try
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const App = () => { return ( <div> <h1>父组件</h1> <Child /> </div> ) }
const Child = () => { return ( <div> <h2>子组件</h2> <Other /> <List /> </div> ) }
const Other = () => { return ( <div> <h3>其他组件</h3> </div> ) }
const List = () => { return ( <div> <h3>列表组件</h3> </div> ) }
export default App
|
可以实现效果
这样就会出现一个问题,就是我子组件里面的组件想要去拿取数据只能是在相对于它自己的父级,涉及到了一个作用域的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const App = () => { const appData = 'App组件数据内容' return ( <div> <h1>父组件</h1> <Child /> </div> ) }
const Child = () => { const childData = 'Child组件数据内容' return ( <div> <h2>子组件</h2> <Other data={childData} /> </div> ) }
const Other = ({ data }) => { return ( <div> <h3>其他组件 :{data}</h3> </div> ) }
export default App
|
所以在实际开发中一般使用 props 里面的另外一个属性 children 属性来渲染组件内的任何子元素
注意是小写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const App = () => { const data = '父组件数据' return ( <div> <h1>父组件</h1> <Child> <Other data={data} /> </Child> </div> ) }
const Child = ({ children }) => { return ( <div> <h2>子组件</h2> {children} </div> ) }
const Other = ({ data }) => { return ( <div> <h3>其他组件{data}</h3> </div> ) }
export default App
|
这样就实现了跨层级之间的数据传递
但是如果我想要传递多个内容并把它放在指定不同的位置,该如何实现呢?
这个和 vue 中的插槽的功能有一些相像,都是向组件内分发内容,不过 vue 是可以通过具名插槽来进行指定的分发和接收
vue 的实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <script setup lang="ts"> import son from './components/son.vue' </script>
<template> <son> <template #header> <h1>我是标题</h1> </template>
<template #default> <p>我是内容</p> </template>
<template #footer> <p>我是底部</p> </template> </son> </template>
<template> <div class="root"> <slot name="header"></slot> <slot name="default"></slot> <slot name="footer"></slot> </div> </template>
|
react的实现方式
在传递数据的时候是可以传递 jsx 的,我们在传递的时候就直接传递不同的内容,对应进行接收使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const App = () => { return ( <div> <h1>父组件</h1> <Child header={<h4>头部标题</h4>} body={<p>内容</p>} /> </div> ) }
const Child = ({ header,body }) => { return ( <div> <h2>子组件</h2> {header} <hr /> {body} </div> ) }
export default App
|
一个组件中与多个相同的组件,数据之间是相互隔离的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { useState } from 'react';
export default function MyApp() { return ( <div> <h1>数据更新</h1> <MyButton /> <MyButton /> </div> ); }
function MyButton() { const [count, setCount] = useState(0);
function handleClick() { setCount(count + 1); }
return ( <button onClick={handleClick}> 当前数据: {count} </button> ); }
|
复杂组件之间的数据通讯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { createContext, useContext } from 'react'
const Context = createContext('')
const App = () => { const msg = '父组件的数据' return ( <div> <Context.Provider value={ msg }> // 顶层组件提供数据 父组件 <Son /> </Context.Provider> </div> ) }
const Son = () => { return ( <div> 子组件 <Grandson /> </div> ) }
const Grandson = () => { return ( <div> 孙子组件 <Lower /> </div> ) }
const Lower = () => { const value = useContext(Context) return ( <div> 底层组件 {value} </div> ) } export default App
|
自定义hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useState } from 'react'
const useToggle = () => { const [value, setValue] = useState(false) const toggle = () => setValue(!value) return [value, toggle] }
const App = () => { const [value, toggle] = useToggle() return ( <div> <button onClick={toggle}>Toggle</button> <div>{value && 'Hello World'}</div> </div> ) }
export default App
|
状态管理
redux
可以理解为vue中的vuex和pinia,进行集中式状态管理
redux toolkit 官方推荐redux逻辑的方式,是一套工具集,简化书写方式
react-redux 用来链接react组件和redux的中间件
安装插件
1
| pnpm i @reduxjs/toolkit react-redux
|
定义 store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({ name: 'user', initialState: { token: '00002' }, reducers: { setToken: (state, action) => { state.token = action.payload; }, delToken: (state) => { state.token = ''; }, }, });
const { setToken, delToken } = userSlice.actions;
const userReducer = userSlice.reducer;
export { setToken, delToken }; export default userReducer;
|
中转配置 store
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { configureStore } from '@reduxjs/toolkit'; import userReducer from './modules/user';
const store = configureStore({ reducer: { user: userReducer, }, });
export default store;
|
注入 store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| # main.tsx
import React from 'react' import ReactDOM from 'react-dom/client' import {RouterProvider} from 'react-router-dom' import { Provider } from 'react-redux' import store from './store' import router from './router'
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <Provider store={store}> <RouterProvider router={router} /> </Provider> </React.StrictMode> )
|
组件中使用store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useSelector, useDispatch } from 'react-redux' import { setToken, delToken } from '../../store/modules/user' function App() { const { token } = useSelector((state: any) => state.user) const dispatch = useDispatch() return ( <> <span>{token}</span> <br /> <button onClick={() => dispatch(setToken('123'))}>设置token</button> <button onClick={() => dispatch(delToken())}>删除token</button> </> ) } export default App
|
Zustand
安装
定义 store
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { create } from 'zustand';
const userStore = create((set) => { return { token: '0002', setToken: (payload) => set({ token: payload }), delToken: () => set({ token: '' }) } })
export default userStore;
|
组件中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import userStore from '../../store/modules/user' function App() { const { token, setToken, delToken } = userStore() return ( <> <span>{token}</span> <br /> <button onClick={() => setToken('123456')}>设置token</button> <button onClick={() => delToken()}>删除token</button> </> ) } export default App
|
切片模式
待施工
Easy-peasy
建立在redux
之上,看单词就能看出来,翻译过来就是非常简单,小菜一碟
直接上手,安装插件
我们在根目录新建一个store
文件夹,再新建一个index.tsx
对所有的状态进行中转,再新建一个modules
对各自的状态进行管理
1 2 3 4 5 6
| src/ |-- store/ | |-- index.js | |-- modules/ | |-- user.js |-- App.js
|
action 用于定义同步状态
- action *回调**中的两个参数 state,*payload
- 第一个参数 state 是当前数据的状态,可以直接修改来更新状态
- 第二个参数 payload 是传递给 action 的数据
thunk 用于定义异步状态
- thunk *回调**中有两个参数 actions,*payload
- 第一个参数 actions 是当前模块的 action,可以调用 action 来更新状态
- 第二个参数 payload 是调用异步函数传递给 thunk 的数据
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| # user.tsx
import { action, thunk } from 'easy-peasy' import request from '@/utils/request'
const user = { token: '00002', setToken: action((state, payload) => { state.token = payload }), delToken: action(state => { state.token = '' }), getToken: thunk(async actions => { const { data } = await request.get('/get/token') actions.setToken(data) }), }
export default user
|
中转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| # index.tsx
import { createStore } from 'easy-peasy'; import user from './modules/user'
const storeModel = { user:user }
const store=createStore(storeModel)
export default store;
|
组件中使用
useStoreState
和 useStoreActions
都接收一个回调函数。这种方式允许你访问和使用 store 的状态或 actions,而不是直接暴露整个 store 对象。这样可以避免不必要的重新渲染,确保组件仅在相关状态或 actions 发生变化时才更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useStoreState, useStoreActions } from 'easy-peasy' const home = () => { const token = useStoreState((state)=> state.user.token) const setToken = useStoreActions((state)=> state.user.setToken) const delToken = useStoreActions((state)=> state.user.delToken) return ( <> <div>{token}</div> <button onClick={()=>setToken('123456')}>设置token</button> <br /> <button onClick={()=>delToken()}>清除token</button> </> ) }
export default home
|
ReactRouter
安装 react-router-dom 包
1 2 3 4 5 6 7 8 9 10 11
| pnpm i react-router-dom import React from 'react' import ReactDOM from 'react-dom/client' import { RouterProvider } from 'react-router-dom' import router from './router'
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <RouterProvider router={router}></RouterProvider> RouterProvider 用来提供路由出口并绑定路由 </React.StrictMode> )
|
配置路由信息
1 2 3 4
| import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home' import Article from '../pages/article'
|
创建路由实例对象
1 2 3 4 5 6 7 8 9 10 11 12
| const router = createBrowserRouter([ { path: '/', element: <Home />, }, { path: '/article', element: <Article />, }, ])
export default router
|
路由跳转
编程式导航
使用 useNavigate
可以理解为 vue 中的 router.push()方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Link , useNavigate} from "react-router-dom"
const Home = () => { const navigate = useNavigate() return ( <div> <Link to={"/article"}>article</Link> <h1>home</h1> <button onClick={() => navigate('/article')}>去文章页面</button> </div> ) } export default Home
|
声明式导航
使用 Link 标签 to 属性后为跳转的路径地址,在实际渲染时会被渲染为 html 的 a 标签
1 2 3 4 5 6 7 8 9 10 11
| import { Link } from "react-router-dom"
const Home = () => { return ( <div> <Link to={"/article"}>article</Link> <h1>home</h1> </div> ) } export default Home
|
路由传参
searchParams传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { useNavigate, Link } from 'react-router-dom'
const home = () => { const navigate = useNavigate() return ( <div> <h1>Home</h1> <button onClick={() => navigate('/article?id=1001&name=react')}>去文章页</button> </div> ) }
export default home
import { Link } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
const Article = () => { const [params] = useSearchParams() const id = params.get('id') const name = params.get('name') return ( <div> <Link to='/home'>回到首页</Link> <h1>article</h1> <p>id:{id}--name:{name}</p> </div> ) } export default Article
|
params传参
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useNavigate, Link } from 'react-router-dom'
const home = () => { const navigate = useNavigate() return ( <div> <h1>Home</h1> <button onClick={() => navigate('/article/1002/react')}>去文章页</button> </div> ) }
export default home
|
去到路由的位置,加上占位符
1 2 3
| const router = createBrowserRouter([ { path: '/article/:id/:tile', element: <Article /> } ])
|
详情页
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Link,useParams } from 'react-router-dom' const Article = () => { const params = useParams() const id = params.id const name = params.name return ( <div> <Link to='/home'>回到首页</Link> <h1>article</h1> <p>id:{id}----name:{name}</p> </div> ) } export default Article
|
嵌套路由
使用children表示对应的二级路由地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home' import Article from '../pages/article'
const router = createBrowserRouter([ { path: '/', element: <Home />, children: [ { path: 'article', element: <Article />, } ] },
])
export default router
|
在要渲染二级路由的地方,放置 Outlet 标签,和vue中的 view-router 是一个意思
1 2 3 4 5 6 7 8 9 10 11
| import { Link , Outlet} from "react-router-dom" const Home = () => { return ( <div> <Link to='/article'>article</Link> <h1>home</h1> <Outlet/> </div> ) } export default Home
|
默认显示二级路由
去掉二级路由的 path 属性替换为 index:true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home' import Article from '../pages/article'
const router = createBrowserRouter([ { path: '/', element: <Home />, children: [ { index: true, element: <Article />, } ] },
])
export default router
|
404 页面配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { createBrowserRouter } from 'react-router-dom'
import Home from '../pages/home' import Article from '../pages/article'
const router = createBrowserRouter([ { path: '/', element: <Home />, children: [ { index: true, element: <Article />, } ] }, { path: '*', element: <div>404</div> }
])
export default router
|
路由懒加载
- 使用 react 内置的lazy函数实现路由懒加载
- Suspense 组件包裹路由中element选项对应的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { createBrowserRouter } from 'react-router-dom' import { lazy, Suspense } from 'react'
const Detail = lazy(() => import('../pages/detail/index')) const Home = lazy(() => import('../pages/home/index'))
const router = createBrowserRouter([ { path: '/', element: ( <Suspense> <Home /> </Suspense> ), children: [ { path: 'detail', element: ( <Suspense> <Detail /> </Suspense> ), }, ], } ])
export default router
|
路由模式
待施工