1、工程化实践
umi dva作为底层框架,Ant Design Mobile为 UI 组件库,是蚂蚁金服推崇的的react项目最佳实践,具有国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面等等方便一线开发者的功能,部分功能代码可以参考Ant Design pro,目录结构预览如下
2、通用组件
- ErrorBoundary
部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界:错误边界 – React
- 懒加载
// index.js
import React, { Component, lazy, Suspense } from 'react';
export default class Index extends Component {
constructor(props) {
super(props);
this.state = {
};
}
_renderLazy = () => {
let Lazy;
const { component, delay, ...other } = this.props;
if (!component || component.constructor.name !== 'Promise') {
Lazy = lazy(() => import('./error'));
} else {
Lazy = lazy(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve(component);
}, delay || 300);
})
});
}
return <Lazy {...other} />
}
render() {
return (
<div>
<Suspense fallback={<div>loading...</div>}>
{this._renderLazy()}
</Suspense>
</div>
)
}
}
代码语言:javascript复制// error.js
import React, { Component } from 'react';
export default class Error extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
组件引入错误!
</div>
)
}
}
3、vs-code插件
- project-tpl
比如输入func则可自动生成hook模板
4、自定义 hook
- 请求hook封装
对发送请求封装成hook后十分整洁,还可以监听数据变化自动发送请求
效果:
代码语言:javascript复制 const [houses, loading] = useHttpHook({
url: '/house/search',
body: {
...page,
houseName,
code: query?.code,
startTime: query?.startTime ' 00:00:00',
endTime: query?.endTime ' 23:59:59'
},
watch: [page.pageNum, houseSubmitName]
});
实现细节:
代码语言:javascript复制// utils/http.js
import { Toast } from 'antd-mobile';
export default function Http({
url,
method = 'post',
headers,
body = {},
setLoading,
setResult,
}){
setLoading && setLoading(true);
const defaultHeader = {
'Content-type': 'application/json'
};
let params;
if(method.toUpperCase() === 'GET'){
params = undefined;
}else {
params = {
headers: {
...defaultHeader,
headers
},
method,
body: JSON.stringify(body)
}
}
return new Promise((resolve, reject)=>{
fetch('/api' url, params)
.then(res => res.json())
.then(res => {
if(res.status === 200){
resolve(res.data);
setResult && setResult(res.data);
}else {
Toast.fail(res.errMsg);
reject(res.errMsg);
}
})
.catch(err => {
Toast.fail(err);
reject(err);
})
.finally(() => {
setLoading && setLoading(false);
})
});
}
代码语言:javascript复制// useHttpHook.js
import { useState, useEffect } from 'react';
import { Http } from '@/utils';
export default function useHttpHook({
url,
method = 'post',
headers,
body = {},
watch = []
}) {
const [result, setResult] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
Http({
url,
method,
headers,
body,
setResult,
setLoading
});
}, watch);
return [result, loading];
}
- 图片懒加载hook
图片在可视区域才加载真实图片
代码语言:javascript复制import { useEffect } from 'react';
import { isEmpty } from 'project-libs';
/**
* 1,监听图片是否进入可视区域;
* 2,将src属性的值替换为真实的图片地址,data-src
* 3,停止监听当前的节点
* @param {*} ele 监听的img元素类名,如 .imgBox
* @param {*} callback
* @param {*} watch 监听的变化数据,如data变化后触发此hook逻辑
*/
let observer;
export default function useImgHook(ele, callback, watch = []){
useEffect(()=>{
const nodes = document.querySelectorAll(ele);
if(!isEmpty(nodes)){
observer = new IntersectionObserver((entries)=>{
callback && callback(entries);
entries.forEach(item => {
// console.log(item)
if(item.isIntersecting){
const dataSrc = item.target.getAttribute('data-src');
item.target.setAttribute('src', dataSrc);
observer.unobserve(item.target);
}
});
});
nodes.forEach(item => {
observer.observe(item);
});
}
return () => {
if(!isEmpty(nodes) && observer){
observer.disconnect();
}
}
}, watch)
}
使用例子
代码语言:javascript复制useImgHook('.' styles.goodImg, (entries:any) => { console.log('entries', entries) }, [tabs, activeTab, categories])
<img className={styles.goodImg} src={require('@/assets/h5/blank.png')} data-src={item['smallImage']} alt=""/>
- 滚动加载hook
滚动到底部再加载数据
代码语言:javascript复制import { useEffect } from 'react';
let observer;
export default function useObserverHook(ele, callback, watch = []) {
useEffect(() => {
const node = document.querySelector(ele);
if (node) {
observer = new IntersectionObserver(entries => {
callback && callback(entries);
});
observer.observe(node);
}
return () => {
if (observer && node) {
// 解绑元素
observer.unobserve(node);
// 停止监听
observer.disconnect();
}
}
}, watch);
}
使用示例
代码语言:javascript复制 /**
* 1,监听loading是否展示出来;
* 2,修改分页数据;
* 3,监听分页数据的修改,发送接口,请求下一页的数据;
* 4,监听loading变化,拼装数据
*/
useObserverHook('#' CommonEnum.LOADING_ID, (entries) => {
// console.log(entries)
if (!loading && entries[0].isIntersecting) {
setPage({
...page,
pageNum: page.pageNum 1
});
}
}, null);
- 骨架屏
替换loading页,显示加载中的页面骨架,给用户更好的浏览体验
src目录下建skeleton文件夹
写骨架屏的静态文件页面,如下
代码语言:javascript复制import React, { useState, useEffect } from 'react';
import ItemBox from '@/components/ItemBox/ItemBox';
// @ts-ignore
import Arrow from '@/assets/h5/arrow.png';
import styles from './index.less'
export default function(props){
const [state, setState] = useState(Array(3).fill(1))
useEffect(() => {
console.log(state)
}, [])
return (
<div className={styles.orderListsSkeletons}>
{state.map((item, index) => {
return <div key={index} className={styles.itemBox}>
<ItemBox
name={name}
smallImage={require('./../../assets/h5/blank.png')}
desc={[`-- 金币`]}
stock={'--'}
style={{ backgroundColor: '#F7F7F7' }}
actions={<img src={Arrow} className={styles.button} />}
/>
</div>
})}
</div>
)
}
之后在没有数据时展示这个骨架屏即可
代码语言:javascript复制data ? <Components /> : <OrderListSkeletons />
5、通用工具函数
- 时间格式化
import dayjs from 'dayjs';
export default function timer(time, type='all'){
return dayjs(time).format(type === 'all' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
- project-libs
是一个常用函数集锦的工具库,包括浏览器、函数式、常用验证、cookie、数组处理等函数
project-libs