原生 Audio、Video;策略模式+动态组件对不同类型消息进行封装 - React
- 创建 
MessageAudioMessageVideo,用于分别展示聊天消息中的音频和视频 
            
            
              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;