原生 Audio、Video;策略模式+动态组件对不同类型消息进行封装 - React
- 创建
MessageAudio
MessageVideo
,用于分别展示聊天消息中的音频和视频
jsx
// MessageAudio.js
import React from 'react';
const MessageAudio = ({ src }) => {
return <audio controls src={src} />;
};
export default MessageAudio;
jsx
// MessageVideo.js
import React from 'react';
const MessageVideo = ({ src }) => {
return <video controls src={src} />;
};
export default MessageVideo;
Message
组件:根据消息类型动态渲染相应的消息组件
jsx
// Message.js
import React from 'react';
import MessageAudio from './MessageAudio';
import MessageVideo from './MessageVideo';
const Message = ({ type, src }) => {
// 定义消息类型到组件的映射关系
const components = {
audio: MessageAudio,
video: MessageVideo
};
// 根据消息类型获取相应的组件
const Component = components[type];
if (!Component) {
return null;
}
return <Component src={src} />;
};
export default Message;
- 在
ChatApp
组件中遍历消息数组,并将每条消息传递给Message
组件进行渲染。根据消息的类型不同,Message
组件会动态选择相应的消息组件进行渲染
jsx
// ChatApp.js
import React from 'react';
import Message from './Message';
const ChatApp = () => {
const messages = [
{ type: 'audio', src: 'audio.mp3' },
{ type: 'video', src: 'video.mp4' }
];
return (
<div>
{messages.map((message, index) => (
<Message key={index} type={message.type} src={message.src} />
))}
</div>
);
};
export default ChatApp;
自定义指令实现按钮级别的权限控制,通过后端返回的字段判断用户角色实现菜单栏权限控制 - Vue,VueRouter,VueX,Element-UI,ES6
- 名为
permission
的自定义指令,用于按钮级别的权限控制
javascript
// permission.js
import Vue from 'vue';
Vue.directive('permission', {
inserted: (el, binding, vnode) => {
const { value } = binding;
const roles = vnode.context.$store.state.roles;
if (value && !roles.includes(value)) {
el.parentNode && el.parentNode.removeChild(el);
}
}
});
- 用这个自定义指令,通过
inserted
钩子函数在指令绑定到元素时进行权限判断。如果当前用户的角色不包含在指定的角色列表中,则移除该按钮元素
vue
<template>
<div>
<!-- 按钮级别的权限控制 -->
<el-button v-permission="'admin'">Admin Button</el-button>
<el-button v-permission="'user'">User Button</el-button>
<!-- 菜单栏权限控制 -->
<el-menu v-if="roles.includes('admin')">
<!-- admin 菜单项 -->
</el-menu>
<el-menu v-else-if="roles.includes('user')">
<!-- user 菜单项 -->
</el-menu>
</div>
</template>
<script>
export default {
computed: {
roles() {
return this.$store.state.roles;
}
}
};
</script>
- 在应用的根组件中引入
permission
自定义指令
javascript
// main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store';
import permissionDirective from './permission';
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount('#app');
// 注册自定义指令
permissionDirective;
Vue,VueRouter,VueX,Element-UI,ES6 基于UI框架对表格组件进行二次封装,外抛 slot 可进行自定义按钮功能,并实现数据格式化处理配置,支持外部自定义配置修改
vue
<!-- CustomTable.vue -->
<template>
<el-table :data="formattedData" stripe border>
<!-- 自定义按钮功能插槽 -->
<template v-if="customButtons" v-slot:action="{ row }">
<el-button v-for="(button, index) in customButtons" :key="index" @click="handleCustomButtonClick(button, row)">
{{ button.text }}
</el-button>
</template>
<!-- 根据配置渲染列 -->
<el-table-column v-for="(column, index) in columns" :key="index" :label="column.label" :prop="column.prop" :formatter="column.formatter">
</el-table-column>
</el-table>
</template>
<script>
export default {
props: {
data: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
},
customButtons: {
type: Array,
default: () => []
}
},
computed: {
formattedData() {
// 数据格式化处理
return this.data.map(item => {
return this.columns.reduce((formattedItem, column) => {
formattedItem[column.prop] = column.formatter ? column.formatter(item[column.prop]) : item[column.prop];
return formattedItem;
}, {});
});
}
},
methods: {
handleCustomButtonClick(button, row) {
// 处理自定义按钮点击事件
button.handler(row);
}
}
};
</script>
-
CustomTable
的 Vue 组件接受三个 propdata
:表格数据数组columns
:表格列配置数组,包括label
(列标题)、prop
(列对应数据字段)、formatter
(数据格式化函数)customButtons
:自定义按钮功能数组,包括text
(按钮文本)和handler
(按钮点击事件处理函数)
-
组件内部通过计算属性
formattedData
对传入的原始数据进行格式化处理,然后在表格中渲染
vue
<template>
<custom-table :data="tableData" :columns="tableColumns" :custom-buttons="customButtons">
<template v-slot:action="{ row }">
<el-button @click="handleEdit(row)">Edit</el-button>
<el-button @click="handleDelete(row)">Delete</el-button>
</template>
</custom-table>
</template>
<script>
import CustomTable from '@/components/CustomTable.vue';
export default {
components: {
CustomTable
},
data() {
return {
tableData: [
{ id: 1, name: 'Item 1', price: 10 },
{ id: 2, name: 'Item 2', price: 20 },
{ id: 3, name: 'Item 3', price: 30 }
],
tableColumns: [
{ label: 'ID', prop: 'id' },
{ label: 'Name', prop: 'name' },
{ label: 'Price', prop: 'price', formatter: this.formatPrice }
],
customButtons: [
{ text: 'Custom Action', handler: this.handleCustomAction }
]
};
},
methods: {
formatPrice(price) {
// 价格格式化处理函数
return `$${price.toFixed(2)}`;
},
handleEdit(row) {
// 编辑操作
console.log('Edit:', row);
},
handleDelete(row) {
// 删除操作
console.log('Delete:', row);
},
handleCustomAction(row) {
// 自定义操作
console.log('Custom Action:', row);
}
}
};
</script>
- 用
CustomTable
组件,并通过v-slot
插槽插入了自定义的按钮功能 - 在父组件中定义了
tableData
、tableColumns
和customButtons
数据,分别表示表格数据、表格列配置和自定义按钮功能
对Axios 进行二次简单封装,配合防抖控制鉴权频率,实现 token 过期之后自动授权并恢复上次请求 - Vue,VueRouter,VueX,Axios,ES6
javascript
// api.js
import axios from 'axios';
import router from '@/router';
import store from '@/store';
// 创建 Axios 实例
const instance = axios.create({
baseURL: 'http://api.example.com',
timeout: 5000
});
// 请求拦截器
instance.interceptors.request.use(
config => {
// 在发送请求之前做些什么
const token = store.state.token;
if (token) {
// 如果存在token,则在请求头中携带token
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
response => {
// 对响应数据做些什么
return response;
},
async error => {
// 对响应错误做些什么
if (error.response.status === 401) {
// 如果是未授权错误,则尝试重新授权
try {
await store.dispatch('refreshToken');
// 重新授权成功后,重新发送之前的请求
return instance(error.config);
} catch (error) {
// 如果重新授权失败,则跳转到登录页面
router.push('/login');
return Promise.reject(error);
}
}
return Promise.reject(error);
}
);
// 导出封装后的 Axios 实例
export default instance;
- 二次封装名为
instance
的 Axios 实例 - 在请求拦截器中,判断是否存在 token,如果存在,则在请求头中携带 token
- 在响应拦截器中,我们捕获了状态码为 401(未授权)的错误,并尝试重新授权
- 如果重新授权成功,则重新发送之前的请求;如果重新授权失败,则跳转到登录页面
javascript
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from '@/api';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: null
},
mutations: {
setToken(state, token) {
state.token = token;
}
},
actions: {
async refreshToken({ commit, state }) {
// 在这里发送重新授权的请求,并更新 token
try {
const response = await axios.post('/refresh_token', { token: state.token });
const newToken = response.data.token;
commit('setToken', newToken);
} catch (error) {
throw error;
}
}
}
});
⾃定义数据 mock ⽅案(React,Typescript,Antd,mobx)
- 创建一个模拟的后端数据文件
mockData.ts
:
typescript
// mockData.ts
const mockData = [
{ id: 1, name: 'Apple', price: 2.5 },
{ id: 2, name: 'Banana', price: 1.8 },
{ id: 3, name: 'Orange', price: 3.2 },
{ id: 4, name: 'Grapes', price: 4.5 },
{ id: 5, name: 'Watermelon', price: 6.0 }
];
export default mockData;
- 创建一个 MobX store 来管理数据状态
ProductStore.ts
:
typescript
// ProductStore.ts
import { makeAutoObservable } from 'mobx';
import mockData from './mockData';
interface Product {
id: number;
name: string;
price: number;
}
class ProductStore {
products: Product[] = [];
constructor() {
makeAutoObservable(this);
// 初始化数据
this.fetchProducts();
}
fetchProducts() {
// 模拟异步获取数据,实际项目中会替换成真实的后端请求
setTimeout(() => {
this.products = mockData;
}, 1000);
}
}
const productStore = new ProductStore();
export default productStore;
- 展示数据并使用 MobX store 中的数据
ProductList.tsx
:
typescript
// ProductList.tsx
import React, { useEffect } from 'react';
import { observer } from 'mobx-react';
import { Table } from 'antd';
import productStore from './ProductStore';
const ProductList: React.FC = () => {
useEffect(() => {
productStore.fetchProducts();
}, []);
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id'
},
{
title: 'Name',
dataIndex: 'name',
key: 'name'
},
{
title: 'Price',
dataIndex: 'price',
key: 'price'
}
];
return (
<Table columns={columns} dataSource={productStore.products} loading={!productStore.products.length} />
);
};
export default observer(ProductList);
- 在根组件中使用
ProductList
组件
typescript
// App.tsx
import React from 'react';
import ProductList from './ProductList';
const App: React.FC = () => {
return (
<div>
<h1>Product List</h1>
<ProductList />
</div>
);
};
export default App;
react采用事件委托的方式获取渲染加入群聊列表
在 React 中,通常不直接使用事件委托,因为 React 提供了更好的方式来处理事件。但是,你可以使用类似事件委托的概念来实现相同的效果,例如在父组件中监听子组件的事件
- 假设我们有一个
ChatList
组件,它渲染了加入群聊列表中的每个聊天项
jsx
// ChatList.js
import React from 'react';
import ChatItem from './ChatItem';
const ChatList = ({ chats }) => {
return (
<ul>
{chats.map((chat, index) => (
<ChatItem key={index} chat={chat} />
))}
</ul>
);
};
export default ChatList;
然后,我们有一个 ChatItem
组件,用于渲染单个聊天项,并在点击时触发相应的事件:
jsx
// ChatItem.js
import React from 'react';
const ChatItem = ({ chat, onClick }) => {
const handleClick = () => {
onClick(chat.id);
};
return (
<li onClick={handleClick}>
{chat.name}
</li>
);
};
export default ChatItem;
现在,我们在父组件中定义一个事件处理函数来处理点击事件,并将其传递给 ChatList
组件:
jsx
// ChatApp.js
import React from 'react';
import ChatList from './ChatList';
class ChatApp extends React.Component {
handleChatItemClick = (chatId) => {
console.log('Clicked chat item with ID:', chatId);
};
render() {
const chats = [
{ id: 1, name: 'Group Chat 1' },
{ id: 2, name: 'Group Chat 2' },
{ id: 3, name: 'Group Chat 3' }
];
return (
<div>
<h2>Join Group Chats</h2>
<ChatList chats={chats} onClick={this.handleChatItemClick} />
</div>
);
}
}
export default ChatApp;
在这个示例中,我们在 ChatApp
组件中定义了一个事件处理函数 handleChatItemClick
,它会在点击聊天项时被调用。然后,我们将这个事件处理函数作为 prop 传递给 ChatList
组件,在 ChatList
组件内部的 ChatItem
组件中调用该事件处理函数。这样,我们就实现了类似事件委托的效果,父组件监听子组件的点击事件。
策略模式
在 React 和 Ant Design(antd)项目中,你可以使用事件代理来处理诸如表单项、列表项等组件的点击事件。以下是一个在 Ant Design 项目中使用事件代理的示例:
假设你有一个 Ant Design 的列表组件,你可以在列表组件的父元素上添加点击事件监听器,然后根据实际点击的元素来执行相应的操作。这个例子中,我们将点击事件委托给列表的父元素,并根据点击的元素来确定是否执行相应的操作。
jsx
import React from 'react';
import { List } from 'antd';
class MyList extends React.Component {
handleClick = (event) => {
// 检查点击的元素是否是列表项
if (event.target.tagName === 'LI') {
// 执行相应的操作,例如获取列表项的内容
console.log('Clicked item:', event.target.textContent);
}
};
render() {
return (
<div onClick={this.handleClick}>
<List
dataSource={['Item 1', 'Item 2', 'Item 3']}
renderItem={item => (
<List.Item>{item}</List.Item>
)}
/>
</div>
);
}
}
export default MyList;
DOM 原生属性+API 实现滚动条上下拉加载更多,采用预加载和节流函数实现流畅的无限滚动效果
在 React 和 Ant Design 中实现滚动条上下拉加载更多的功能,同时采用预加载和节流函数实现流畅的无限滚动效果,可以结合使用 onScroll
事件、DOM 原生属性和节流函数
jsx
import React, { useState, useEffect } from 'react';
import { List } from 'antd';
import throttle from 'lodash/throttle'; // 导入节流函数
const MyList = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
// 模拟初始数据
setData(Array.from({ length: 10 }, (_, index) => `Item ${index + 1}`));
}, []);
const handleScroll = throttle(() => {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
// 判断是否滚动到底部并且没有正在加载数据
if (scrollTop + clientHeight >= scrollHeight - 100 && !loading) {
// 模拟加载更多数据
setLoading(true);
setTimeout(() => {
setData(prevData => [
...prevData,
...Array.from({ length: 10 }, (_, index) => `Item ${prevData.length + index + 1}`)
]);
setLoading(false);
}, 1000);
}
}, 200);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [handleScroll]);
return (
<List
dataSource={data}
renderItem={item => (
<List.Item>{item}</List.Item>
)}
loading={loading}
/>
);
};
export default MyList;