一、react项目创建
vite搭建
1、创建项目
pnpm create vite music-web --template react-ts其中,react-demo 是当前项目根目录的文件名,可自行定义。
2、下载依赖包
cd react-demoyarn3、启动项目
yarn dev# 或npm run devCRA搭建
React 官方基于 webpack 发布了一个脚手架工具 Create React App(简称 CRA),用来搭建 React 项目。
1、创建项目
npx create-react-app react-project通过以上命令创建的 React 项目,会自动将项目所需的所有依赖包都下载下来。
2、启动项目
yarn start# 或npm start二、JSX语法
JSX 语法:允许将HTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不 会带来任何副作用,反而会让代码更加直观并易于维护。
语法基础
在标签内部可以使用{} 写js代码
1、根节点
JSX 中要求每一个组件必须有一个根节点,通常情况下,我们可以使用空标签来作为组件的根节点:
export default class App extends Component {
// 渲染组件
render() {
return (
<>
<h1>你好</h1>
<HelloWorld></HelloWorld>
</>
)
}
}2、渲染动态数据
let name = '张三';
export default class MyComponent extends Component {
render() {
return (
<div>
<h1>你好,{name}</h1>
</div>
)
}
}3、渲染动态属性
let key = 'username';
export default class MyComponent extends Component {
render() {
return (
<div>
<input type="text" name={key} />
</div>
)
}
}4、数学运算
et num1 = 100;
let num2 = 20;
export default class MyComponent extends Component {
render() {
return (
<div>
<h1>{num1 + num2}</h1>
</div>
)
}
}5、三元运算符
let age = 20;
export default class MyComponent extends Component {
render() {
return (
<div>
<h1>{age >= 20 ? '成年' : '未成年'}</h1>
</div>
)
}
}6、函数的调用
function isAge() {
if (age >= 18) {
return '成年'
}
return '未成功'
}
export default class MyComponent extends Component {
render() {
return (
<div>
<h1>{isAge()}</h1>
</div>
)
}
}特殊属性
<div className='box'></div>
<label htmlFor="username"></label>列表渲染
保证{}内是数组就可以
let arr = ['张三', '李四', '王五'];
export default class MyComponent extends Component {
render() {
return (
<div>
<ul>
{
arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
}
}三、组件
React 中根据组件的形式分为两种组件:
- 类组件:用的 ES6 中 class 的语法来创建组件
- 函数组件:用的函数的语法来创建组件
类组件
1、创建类组件
import React, { Component } from 'react'
export default class App extends Component {
// 渲染组件
render() {
return (
<h1>你好</h1>
)
}
}快捷键:rcc
2、嵌套组件
import React, { Component } from 'react'
import HelloWorld from './components/helloWorld/HelloWorld'
export default class App extends Component {
// 渲染组件
render() {
return (
<div>
<h1>你好</h1>
<HelloWorld></HelloWorld>
</div>
)
}
}组件样式
1 内联样式
<h1 style={{ color: 'red', backgroundColor: '#eee' }}>你好</h1>2 外部样式
① 全局样式
外部的全局样式文件,对于文件名没有特殊要求,按照常规的样式文件后缀名(例如:.css、.scss、.less)即可。
在任意一个组件中通过 import 引入后,都会作用于当前项目中所有的组件。
import './index.css';② 局部样式
外部的局部样式文件,需要满足以下几个条件:
- 文件名必须以
.module.css、.module.scss等为结尾; - 样式选择器只能使用 class 类选择器;
在引入局部样式文件时,需要通过 import ... from 来引入:
import styles from './index.module.css';使用时,将类选择器作为 styles 身上的一个属性来使用:
<h1 className={styles.bgColor}>hello</h1>类组件状态
快捷键:rce
一、定义类组件的状态
这是一个语法糖,真正写全是定义在 construct里面
export default class StateComponent extends Component {
state = {
count: 10,
msg: 'hello'
}
render() {
return (
<div>StateComponent</div>
)
}
}二、访问类组件的状态
推荐解构
export default class StateComponent extends Component {
state = {
count: 10,
msg: 'hello'
}
render() {
const { count, msg } = this.state;
return (
<div>
<h1>{this.state.count}</h1>
<h2>{count}</h2>
<h1>{this.state.msg}</h1>
<h1>{msg}</h1>
</div>
)
}
}三、修改类组件的状态
使用this.setState()
export default class StateComponent extends Component {
state = {
count: 10,
msg: 'hello'
}
render() {
const { count, msg } = this.state;
return (
<div>
<h1>{this.state.count}</h1>
<h2>{count}</h2>
<h1>{this.state.msg}</h1>
<h1>{msg}</h1>
<button onClick={() => {
// 修改state数据
this.setState({
msg: '你好',
count: count + 1
});
}}>修改 msg</button>
</div>
)
}
}四、setState 的扩展
1、setState 是异步方法
如果要查看修改后的数据,可以通过 setState 的第二个参数 —— 回调函数,来输出查看:
this.setState({
msg: '你好',
count: count + 1
}, () => {
console.log(this.state.msg, this.state.count);
});2、setState 会触发 render
更新组件状态:setState 修改完数据后,会重新触发 render 方法的执行,让组件重新渲染,更新页面。
合并更新:setState方法并不会立即改变组件的状态,而是将要更新的状态合并到当前状态中,然后在下一个自然渲染周期中应用这些状态的变化。这样做是为了优化性能,防止频繁的重新渲染。
类组件中的事件
一、绑定事件
最好都写一层箭头函数 (e)=>{},但得注意组件刷新函数地址会变动,有些隐患。
export default class EventComponent extends Component {
render() {
return (
<div>
<button onClick={() => {
console.log('事件绑定成功');
}}>按钮</button>
</div>
)
}
}二、事件方法
export default class EventComponent extends Component {
sayHello = () => {
console.log('hello');
}
render() {
return (
<div>
<button onClick={this.sayHello}>按钮</button>
</div>
)
}
}三、事件传参
export default class EventComponent extends Component {
sayHello = (name) => {
console.log('hello', name);
}
render() {
return (
<div>
<button onClick={() => {
this.sayHello('张三');
}}>张三</button>
<button onClick={() => this.sayHello('李四')}>李四</button>
</div>
)
}
}四、事件对象
是合成对象,但有target
export default class EventComponent extends Component {
render() {
return (
<div>
<button onClick={(event) => {
event.preventDefault();
event.stopPropagation();
}}>按钮</button>
</div>
)
}
}createRef 的使用
一、调用 createRef
React 中提供了一个 createRef 的方法,用来注册绑定节点:
import React, { Component, createRef } from 'react'
export default class TodoList extends Component {
// 调用 createRef,得到一个 ref 对象
inputRef = createRef();
render() {
return (
<>
<div>
{/* 通过 ref 属性,绑定 ref 对象 */}
<input type="text" ref={this.inputRef} />
<button>新增</button>
</div>
</>
)
}
}二、通过 ref 获取节点
当节点身上通过 ref 属性绑定了 createRef 的对象后,后续我们就可以通过 createRef 的对象来获取对应节点:
import React, { Component, createRef } from 'react'
export default class TodoList extends Component {
inputRef = createRef();
addTodo = () => {
// 获取 input 节点
console.log(this.inputRef.current);
}
render() {
return (
<>
<div>
<input type="text" ref={this.inputRef} />
<button onClick={this.addTodo}>新增</button>
</div>
</>
)
}
}三、使用ref存储数据
可以使用他的current存储数据,但是这个数据更新不会触发页面更新。当数据变动不需要页面更新的时候可以使用。
类组件生命周期

一 常用生命周期
1、render
render 方法,会在组件首次渲染和组件更新时执行。作用是:用于渲染首次挂载的虚拟节点,以及后续每次数据更新后,渲染新的虚拟节点。这里能获取数据,在渲染器更改数据。
2、componentDidMount
componentDidMount 方法,会在组件挂载完成后执行。作用是:用来发送网络请求。
3、componentWillUnmount
componentWillUnmount 方法,会在组件卸载前执行。作用是:清除组件中的计时器。
4、示例代码
export default class ClassLifecycle extends Component {
componentDidMount() { console.log('componentDidMount 组件挂载完成'); }
componentWillUnmount() { console.log('componentWillUnmount 组件卸载前'); }
render() {
console.log('render')
return ( <div>ClassLifecycle</div> )
}
}二、类组件性能优化
1、shouldComponentUpdate
在 React 中,类组件是否更新,不是取决于数据是否发生改变,而是看是否调用了 setState() 方法。一旦 setState() 方法被调用,render() 方法就会重新执行,然后重新渲染组件。
这样的处理方式,会导致出现很多没有必要的 render 渲染。因此,为了解决这个问题,我们就可以借助 shouldComponentUpdate 生命周期函数。
在该函数中,可以通过参数获取到变化后的 state,以及变化的前的 state。通过对两个 state 进行比较,来返回一个布尔值,决定 render 是否要执行。
import React, { Component } from 'react'
export default class LifeCycle extends Component {
state = {
count: 1
}
shouldComponentUpdate(props, state) {
if(state.count == this.state.count) {
return false;
}
return true;
}
render() {
return (
<div>
<button onClick={() => {
this.setState({
count: 1
});
}}>按钮</button>
</div>
)
}
}2、PureComponent
PureComponent 纯组件内部已经将 shouldComponentUpdate 自动处理好了。但是是对state进行的浅比较。
import React, { PureComponent } from 'react'
export default class ClassLifeCycle extends PureComponent {
render() {
return (
<div></div>
)
}
}父子组件间的通信
一、父传子:props
通过属性传值
1、父组件传值
export default class Father extends Component {
state = {
gender: '男'
}
render() {
return (
<div>
Father
<Child name="张三" age={20} gender={this.state.gender}></Child>
</div>
)
}
}2、子组件接收值
export default class Child extends Component {
// constructor(props) {
// super(props);
// }
render() {
const { name, age, gender } = this.props;
return (
<div>
<p>{name}</p>
<p>{age}</p>
<p>{gender}</p>
</div>
)
}
}注意:子组件中通过 props 接收到数据是“只读”数据,不能修改 props 的数据。
3、props 的默认值
export default class Child extends Component {
static defaultProps = {
gender: '女'
}
}4、props 的验证
import React, { Component } from 'react'
import pt from 'prop-types'
export default class Child extends Component {
// 设置 props 的类型
static propTypes = {
age: pt.number,
name:pt.oneOfType([pt.string, pt.number]) //设置多种
}
}列举几个常用的类型:
- 字符串:
pt.string - 数字:
pt.number - 布尔值:
pt.bool - 数组:
pt.array - 对象:
pt.object - 函数:
pt.func - 多个类型中的任意一个:
pt.oneOfType([pt.string, pt.number, ...])
二、子传父:回调函数
1、父组件传递函数
export default class Father extends Component {
getChildData = (data) => {
console.log('子组件传递的数据', data);
}
render() {
return (
<div>
Father
<Child getChildData={this.getChildData}></Child>
</div>
)
}
}2、子组件调用函数并传值
export default class Child extends Component {
render() {
const { getChildData } = this.props;
return (
<div>
<button onClick={() => {
getChildData('hello');
}}>传值</button>
</div>
)
}
}非父子组件之间的通信
一、兄弟组件传值:事件总线
1、下载插件
yarn add events2、创建事件总线
我们手动创建一个 events.js 文件:
import { EventEmitter } from 'events';
const events = new EventEmitter();
export default events;3、监听事件
在负责接收数据的组件中,引入事件总线对象,并添加事件:
import events from './events'
export default class ChildB extends Component {
componentDidMount() {
// 往 events 身上添加了一个 getData 的方法,后续谁调用该方法,就可以传值给组件B
events.addListener('getData', (data) => {
console.log('接收兄弟组件传递的参数', data);
});
}
render() {
return (
<div>ChildB</div>
)
}
}4、派发事件
在负责传递数据的组件中,引入事件总线对象,并触发事件:
import events from './events'
export default class ChildA extends Component {
render() {
return (
<div>
ChildA
<button onClick={() => {
// 调用 events 身上绑定的 getData 方法,并传值
events.emit('getData', '你好');
}}>传值</button>
</div>
)
}
}二、多级嵌套组件传值:context
1、创建 Context
我们手动创建一个 context.js 文件,用来创建 Context 对象:
import { createContext } from "react";
const Context = createContext();
export default Context;2、顶层组件传值
import React, { Component } from 'react'
import ChildA from './ChildA'
// 引入外部的 Context
import Context from './context'
export default class Father extends Component {
state = {
name: '张三'
}
render() {
return (
{/* 通过 Context.Provider 设置需要传递的数据, value 对象用来设置需要传递的数据 */}
{/* 只要是 Context.Provider 包裹的后代都可以接收值*/}
<Context.Provider value={{ name: this.state.name, age: 20 }}>
<ChildA></ChildA>
</Context.Provider>
)
}
}3、内层组件接收值
这个后代组件不管是第几层都可以接收
import React, { Component } from 'react'
import Context from './context'
export default class Test extends Component {
render() {
return (
<Context.Consumer>
{
(value) => {
// value 用来接收顶层组件传递的数据
console.log(value);
return (
<div>
<h1>第四层子组件</h1>
</div>
)
}
}
</Context.Consumer>
)
}
}React插槽
这是类似vue插槽的功能,较真的说并不是插槽。是利用props上的children而已
插槽的作用,就是父组件可以通过插槽传递节点给子组件,子组件中接收到节点后进行渲染。
一、父组件传递节点
我们在父组件 CouponsAdd 中,使用子组件 CoverModal,并往子组件中包裹一组标签:
import CoverModal from './CoverModal'
export default class CouponsAdd extends Component {
render() {
return (
<div>
<CoverModal>
<h3>新增优惠券</h3>
<div>
<label>优惠券名称</label>
<input type="text" />
</div>
<div>
<label>优惠券面值</label>
<input type="text" />
</div>
</CoverModal>
</div>
)
}
}二、子组件渲染节点
子组件中通过 this.props.children 接收到父组件传递的节点,并渲染:
export default class CoverModal extends Component {
render() {
return (
<div className='cover'>
<div className='form'>
{this.props.children}
</div>
</div>
)
}
}函数组件
快捷键:rface
在 React 16.8 版本之前,函数组件也称为“无状态组件”,函数组件没有自己的内部数据,也没有自己的生命周期函数。
从 16.8 开始,React 中专门为函数组件新增了 Hook 方法,这些方法就是用来解决函数组件没有状态、没有生命周期的问题。
一、useState
1、定义初始数据
useState 用于在函数组件内部定义数据。
import { useState } from 'react';
const App = () => {
const [变量, 方法] = useState(初始值);
return (...)
}说明:
变量:用于接收 useState 中定义的数据;方法:调用该方法,用于修改变量的数据值,等同于类组件中的this.setState();
栗子:
const RolesPage = () => {
const [rolesData, setRolesData] = useState([
{ _id: 1, name: '超级管理员', createTime: '2020' },
{ _id: 2, name: '普通管理员', createTime: '2022' },
{ _id: 3, name: '财务', createTime: '2023' }
]);
// 删除角色
const deleteRole = (id) => {
// 调用setRolesData 修改 rolesData
setRolesData(rolesData.filter(item => item._id != id));
}
return (
{/*使用rolesData */}
<Table columns={columns} dataSource={rolesData} rowKey="_id" />
)
}二、 父组件调用子组件的方法
1、父组件创建ref绑定子组件
import { useRef } from 'react';
const Father = () => {
const childRef = useRef();
return (
<Child ref={childRef}></Child>
)
}2、子组件获取ref
import { forwardRef } from 'react'
// ref 的第二个位置的形式参数
const Child = forwardRef((props, ref) => {
})
export default Child;3、子组件暴露方法
import { useImperativeHandle } from 'react';
const Child = (props, ref) => {
useImperativeHandle(ref, () => {
return {
// 返回给父组件的数据或方法 ,方法可以有形参
showModal(params){
}
}
})
}4、父组件接收子组件传递的数据或方法
import { useRef } from 'react';
const Father = () => {
const childRef = useRef();
const fatherEvent = () => {
console.log(childRef.current); // 子组件暴露给父组件的东西 在childRef.current里面
childRef.current.showModal();
}
return (
<>
<button onClick={fatherEvent}></button>
<Child ref={childRef}></Child>
</>
)
}三、生命周期
从 React 16.8 开始,新增了 useEffect 的 Hook 方法,用于在函数组件中来模拟生命周期。
useEffect 接收两个参数,第一个为回调函数,会在指定时机执行。第二参数为数组,当数组指定的数据发送改变便执行第一个参数传递的函数。 useEffect return 一个函数,此函数会在销毁的时候执行
1、没有第二个参数
import {useEffect } from 'react';
useEffect(() => {
// 组件挂载完成时执行 和 组件更新时执行
})2、第二参数为空数组
useEffect(() => {
// 组件挂载完成时执行
}, [])3、第二个参数为非空数组
useEffect(() => {
// 组件挂载完成时执行
// 数组中任意一条数据发生改变时执行
}, [数据1, 数据2])4、返回新函数
useEffect(() => {
return () => {
// 清除订阅
// 组件销毁前执行
}
}, [])四、函数组件性能优化
useMemo
useMemo 用来缓存数据。
import { useMemo } from 'react';
const App = () => {
const result = useMemo(() => {
return 计算的结果;
}, [依赖的数据项])
}React.memo()
React.memo() 用来缓存组件。
import { memo } from 'react';
const App = () => {
}
export default memo(App);在函数组件中,默认情况下父组件更新,子组件也会同步更新。
React.memo() 的作用可以用来缓存组件,缓存后的组件,只要组件内部的 state 或 props 没有发生改变,组件就不会更新。
useCallback
useCallback 用来缓存方法。
函数组件中的方法,在组件更新时,所有的方法默认会重新创建。
import { useCallback } from 'react';
const App = () => {
const sayHello = () => {}
const sayHelloCb = useCallback(sayHello, []);
}immer
- 深度拷贝,但是深拷贝的成本较高,会影响性能;
- ImmutableJS,非常棒的一个不可变数据结构的库,可以解决上面的问题,但跟 Immer 比起来,ImmutableJS 有两个较大的不足:
- 需要使用者学习它的数据结构操作方式,没有 Immer 提供的使用原生对象的操作方式简单、易用;
- 它的操作结果需要通过toJS方法才能得到原生对象,这使得在操作一个对象的时候,时刻要主要操作的是原生对象还是 ImmutableJS 的返回结果,稍不注意,就会产生问题;
react-router
一、下载路由插件
yarn add react-router-dom二、配置一级路由
1、创建路由组件
通常,我们把参与路由配置的组件,称为“页面组件”。这些组件我们都放在 src/pages 目录中。
例如我们需要登录页和布局页:
src
|--- pages
| |--- login
| | |--- LoginPage.jsx
| |--- index
| | |--- IndexPage.jsx2、路由配置
路由懒加载,当用户访问当前路由时,才开始加载对应的组件。
import React, { Suspense, lazy } from 'react';
import { HashRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
// lazy 懒加载
const NotFound = lazy(() => import('./pages/404/NotFound'));
const App = () => {
return (
<Suspense fallback={<p>页面加载中...</p>}>
<BrowserRouter>
<Routes>
{/* path :路由地址 , element: 组件*/}
<Route path="*" element={<NotFound />}></Route>
</Routes>
</BrowserRouter>
</Suspense>
)
}3、路由出口配置
import { Outlet } from 'react-router-dom';
{/* 配置二级路由出口 */}
<Outlet></Outlet>三、 路由跳转
1、Link 标签跳转
import { Link } from 'react-router-dom'
const LoginPage = () => {
return (
<div>
<Link to="/">去首页</Link>
<Link to="/register">没有账号?去注册</Link>
</div>
)
}2、事件跳转
import { useNavigate } from 'react-router-dom'
const LoginPage = () => {
const navigate = useNavigate();
return (
<div>
<button onClick={() => {
// 跳转到 IndexPage
navigate('/');
// 跳转后不保留记录
navigate('/', { replace: true });
}}>登录</button>
</div>
)
}四、二级路由
二级路由,是直接嵌套在一级路由的配置中。
import IndexPage from '@p/index/IndexPage';
import UsersPage from '@p/index/users/UsersPage';
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<IndexPage />}>
{/* 配置 IndexPage 的子路由 */}
<Route path="users" element={<UsersPage />}></Route>
</Route>
</Routes>
</BrowserRouter>
)
}配置二级路由出口
我们在一级路由对应的组件中,通过 配置子路由的出口:
// 引入路由出口组件
import { Outlet } from 'react-router-dom';
const IndexPage = () => {
return (
<Layout className={styles.indexLayout}>
<Layout>
<Content>
<div>
{/* 配置二级路由出口 */}
<Outlet></Outlet>
</div>
</Content>
</Layout>
</Layout>
)
}五 路由传参
1、动态路由传参
我们可以将需要传递的参数拼接到路由的路径中,来实现动态路由的传参。
<Link to={`/goods/update/${value}`}>修改</Link>其中,value 是我们要传递的动态参数的变量名。
2、配置动态路由
在配置路由时,动态路由的配置和普通路由也有区别:
<Route path="goods/update/:id" element={<GoodsUpdate />}></Route>:id 中的 id 是任意变量名,用来匹配路由路径中动态的部分。
3、组件中接收参数
在组件中,通过调用 useParams 来接收动态路由的参数。
import { useParams } from 'react-router-dom'
const GoodsUpdate = () => {
const params = useParams();
console.log('接收到的参数', params);
return (
<div>GoodsUpdate</div>
)
}状态机 Redux
一、安装 redux
yarn add redux react-redux二、仓库初始化
1、创建配置文件
src
|--- store
| |--- index.js # 主仓库的配置文件2、创建仓库对象
import { legacy_createStore } from 'redux'
const store = legacy_createStore();
export default store;3、全局挂载仓库
在项目的入口文件 main.jsx 或者 index.js 中添加以下代码:
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);三、配置仓库模块
我们以角色模块为例。
1、创建模块文件
src
|--- store
| |--- roles
| | |--- reducer.js2、定义初始数据
import { SET_ROLE } from "../actionsConfig";
const initData = []; //初始化默认数据
export const rolesReducer = (state = initData, actions) => {
switch (actions.type) {
case SET_ROLE:
return actions.payload;
break;
default:
return state;
break;
}
};3、合并 reducer
在仓库的主文件中,引入 reducer 函数,通过 combineReducers 方法,将所有的 reducer 函数合并在一起,最后添加到仓库中:
import { legacy_createStore, combineReducers } from 'redux'
// 引入角色的 reducer
import { rolesReducer } from './roles/reducer';
// 合并 reducer
const allReducers = combineReducers({
roles: rolesReducer
})
const store = legacy_createStore(allReducers);4、组件中获取仓库数据
import { useSelector } from 'react-redux';
const RolesPage = () => {
const roles = useSelector((state) => {
return state.roles;
});
console.log(roles);
}当仓库中的数据发生改变时,useSelector 会自动重新获取最新的数据。
5、组件中触发 reducer
import { useDispatch } from 'react-redux';
const RolesPage = () => {
const dispatch = useDispatch();
// 删除角色
const deleteRole = (id) => {
// 删除仓库中的数据
dispatch({ type: 'deleteOne', payload: { id } });
}
}四、处理异步方法,一般用于提取网络请求
目前处理 Redux 异步操作的中间件主要有两种:
- redux-thunk
- redux-saga:基于 ES6 中 generator(生成器)语法
一、下载 redux-thunk
yarn add redux-thunk二、配置中间件
// store/index.js
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
const store = legacy_createStore(allReducers, applyMiddleware(thunk));中间件配置成功后,我们的 redux 中,就可以处理公共的异步方法了。
三、处理公共的异步方法
1、创建文件
src
|--- store
| |--- users
| | |--- actions.js # 异步方法 和reducer同级
| | |--- reducer.js2、提取公共异步方法
import roles from '@/apis/roles';
import { SET_ROLE } from '../actionsConfig';
// 手动设置状态机数据
export const setRolesAsync = () => {
return async (dispatch) => {
const res = await roles.findRoles();
if (1 == res.code) {
dispatch({ type: SET_ROLE, payload: res.data });
}
};
};
// 从后台获取数据
export const getRolesAsync = () => {
return async (dispatch,id) => {
const res = await roles.findRoleById({ roleId: id});
if (1 == res.code) {
dispatch({ type: SET_ROLE, payload: res.data });
}
return res.code;
};
};
export default { setRolesAsync,getRolesAsync };3、组件调用仓库的异步方法
import { useDispatch, useSelector } from 'react-redux';
import actionsRole from '../../store/roles/actions';
const loadData = () => {
const dispatch = useDispatch();
// 获取角色数据
const getUsers = async () => {
// 调用仓库中公共的异步方法
dispatch(actionsRole.setRolesAsync())
}
}五 、自定义 Hook
更高级的异步/网络请求分离
一、基本语法
自定义 Hook,实际上就是自己创建一个函数。但是 Hook 函数和普通函数的区别在于:
- Hook 函数命名时必须以 use 开头;
- Hook 函数可以使用其他的 Hook 方法,但是普通函数中不行;
const useRequest = () => {}二、应用场景
可以将状态机中所有公共的异步方法封装到自定义 Hook 中,来代替 thunk 中间件的使用。
1、创建 Hook 目录
src
|--- hooks
| |--- useRequest.js2、封装自定义 Hook
感觉像是装饰器,过一遍之后返回处理好的(或者实际需要的函数)
import { useDispatch, useSelector } from 'react-redux';
import roles from '@/apis/roles';
import { SET_ROLE } from '../store/actionsConfig';
const useRequest = () => {
const dispatch = useDispatch();
const rolesStore = useSelector((state) => state.roles);
const setRolesAsync = async () => {
const res = await roles.findRoles();
if (1 == res.code) {
dispatch({ type: SET_ROLE, payload: res.data });
}
};
const getRolesAsync = async (id) => {
const res = await roles.findRoleById({ roleId: id });
if (1 == res.code) {
dispatch({ type: SET_ROLE, payload: res.data });
}
return res.code;
};
return { rolesStore, setRolesAsync,getRolesAsync };
};
export default useRequest;3、组件中调用自定义 Hook
import useRequest from '../../hooks/useRequest';
const RoleClass = () => {
//rolesStore :状态机数据
const { rolesStore, setRolesAsync, getRolesAsync } = useRequest();
const loadData = async () => {
setRolesAsync();
};
useEffect(() => {
loadData();
}, []);
}高阶组件,HOC
一、概念
高阶组件,HOC。
高阶组件本质上是一个函数,它和普通的区别在于:
- 该函数需要接收一个组件作为参数
- 最终需要返回一个功能增强的组件
二、处理按钮权限
1、创建目录
src
|--- hoc
| |--- withAuth.jsx
| |--- AuthButton.jsx2、封装按钮组件
在 AuthButton.jsx 组件中,对 antd 的 Button 组件做进一步的封装:
import React from 'react'
import { Button } from 'antd'
const AuthButton = (props) => {
return (
<Button {...props}>{props.children}</Button>
)
}
export default AuthButton3、封装 HOC
在 withAuth.jsx 文件中封装 HOC:
const withAuth = (Component) => (props) => {
const { role } = JSON.parse(localStorage.userInfo || '{}');
if (role.name == '超级管理员') {
return <Component {...props} />
}
// return null; // 不显示按钮
// 显示禁用按钮
return <Component disabled={true} {...props} />
}
export default withAuth;
---------------------------------------
import { getLogin } from '@/utils/utils';
const withAuth = (Component) => {
return (props, ref) => {
const login = getLogin();
const userPower = login ? login.userInfo.role.name : '';
if ('超级管理员' === userPower) {
return <Component {...props}></Component>;
} else {
return <Component disabled {...props}></Component>;
}
};
};
export default withAuth;4、使用 HOC处理自定义组件
封装好 HOC 后,需要将 AuthButton 传给 HOC 进行处理:
import React, { memo } from 'react';
import { Button } from 'antd';
import withAuth from '../withAuth';
const AuthButton = (props) => {
console.log("AuthButton 执行")
return <Button {...props}> {props.children} </Button>;
};
export default memo(withAuth(AuthButton)) ;5、页面使用权限按钮
因为我们的 AuthButton 是基于 antd 的 Button 封装的,所以 Button 的属性都可以设置在 AuthButton 上:
import AuthButton from '../../../hoc/button/AuthButton';
<AuthButton danger onClick={singleton(delData,record._id,record._id)}>
删除
</AuthButton>singleton 函数为一个自定义的装饰器,配合React.memo()
/**
* 场景:子组件为按钮,当父组件更新时由于onclick每次都会产生新函数,导致按钮刷新
* 伪单件模式,为每个按钮提前生成执行函数,避免父组件刷新的时候产生新函数,导致子组件渲染
* 牺牲空间换性能,更具场景取舍
*/
let instantiate = []; //将函数处理为单件模式,刷新的时候不需要生成新的事件函数,同种类型的
export function singleton(fn, key, ...param) {
if (!instantiate[key]) {
instantiate[key] = () => {
fn(...param);
instantiate[key] = ''; // 执行过就清空了,不要这行只能执行一次
return;
};
}
return instantiate[key];
}6 逻辑复用
自定义hook
import { useDispatch, useSelector } from 'react-redux';
import { SET_SYS_THEME } from '../store/actionsConfig';
const useSystem = () => {
const dispatch = useDispatch();
const systemStore = useSelector((state) => state.system);
const setSystemTheme = (type) => {
dispatch({ type: SET_SYS_THEME, payload: type });
};
return { systemStore, setSystemTheme };
};
export default useSystem;React 中使用 Echarts
一、下载
yarn add echarts echarts-for-react二、使用 echarts
1、引入
import ReactEcharts from 'echarts-for-react'2、定义图表数据
- 推荐使用数据集,dataset。ECharts 4 开始支持
const option = {
title: {
text: 'ECharts 入门示例',
},
legend: {},
// 工具栏
toolbox: {
show: true,
orient: 'vertical',
right: 0,
top: 0,
iconStyle: { borderColor: '#000' }, //图形描边颜色,就设置这个
feature: {
//开启对应工具按钮
// dataView: { readOnly: false },
// restore: {}, //配置还原
saveAsImage: { title: '下载' },
},
},
tooltip: {},
dataset: {
// 用 dimensions 指定了维度的顺序。直角坐标系中,如果 X 轴 type 为 category,
// 默认把第一个维度映射到 X 轴上,后面维度映射到 Y 轴上。
// 如果不指定 dimensions,也可以通过指定 series.encode
// 完成映射,参见后文。
dimensions: ['product', '2015', '2016', '2017'],
source: [
{ product: 'Matcha Latte', 2015: 43.3, 2016: 85.8, 2017: 93.7 },
{ product: 'Milk Tea', 2015: 83.1, 2016: 73.4, 2017: 55.1 },
{ product: 'Cheese Cocoa', 2015: 86.4, 2016: 65.2, 2017: 82.5 },
{ product: 'Walnut Brownie', 2015: 72.4, 2016: 53.9, 2017: 39.1 },
],
},
xAxis: {
type: 'category',
},
yAxis: {},
series: [
{
type: 'line',
},
{
type: 'line',
},
{
type: 'line',
},
],
};3、渲染图表
const Home = () => {
return <ReactEcharts option={option} style={{ width: '600px' }}></ReactEcharts>;
};