多级选项卡,应该仍有少部分人,会选择写死。其实是有必要去动态渲染的。
毕竟,代码虽然服务于产品,但是产品的支撑性离不开代码的健壮度。
先上代码:
// ...
import { hashToObj, tabArrBindClick } from '../../../lib/kids-tools';
// ...
const CategoryPage = ({
...props
}) => {
const {
location: { hash },
history: { push }
} = props;
// const tabs = ...
const tags = tabArrBindClick([{
title: '全部',
type: 'all'
}, {
title: '精选',
imgSrc: img_king,
bcgColor: 'yellow',
type: 'featured'
}, {
title: '光环版 + mBuild',
bcgColor: 'blue',
type: 'light'
}, {
title: '程小奔',
bcgColor: 'blue',
type: 'cxb'
}, {
title: 'Python',
bcgColor: 'green',
type: 'python'
}], hash, push, 'tag', '/category');
// const digitalTabs = ...
const [tabIndexs, setTabIndexs] = useState({
type: getTabIndexs[0], // 分类选项卡
tag: getTabIndexs[1], // 子标签
order: getTabIndexs[2] // 热度最新
});
// hash变化选项卡重渲
useEffect(() => {
hashChangeTabIndexs();
}, [hash]);
// 设置索引
const hashChangeTabIndexs = () => {
// 哈希获取索引顺序
const [typeIndex, tagIndex, orderIndex] = getTabIndexs();
setTabIndexs({
type: typeIndex === -1 ? 0 : typeIndex,
tag: tagIndex === -1 ? 0 : tagIndex,
order: orderIndex === -1 ? 0 : orderIndex
})
}
// 获取哈希对应索引
function getTabIndexs() {
// 将hash转成对象
const { type, tag, order } = hashToObj(hash);
// 根据值和数组内的标识(type)进行索引定位。
const typeIndex = tabs.findIndex(i => i.type === type);
const tagIndex = tags.findIndex(i => i.type === tag);
const orderIndex = digitalTabs.findIndex(i => i.type === order);
return [typeIndex, tagIndex, orderIndex];
}
return (
// ...
<NavTabs
tabs={tabs}
activeIndex={tabIndexs.type}
liStyle={liStyle}
narrow={true}
/>
<Tags
tags={tags}
activeIndex={tabIndexs.tag}
/>
<DigitalTabs
tabs={digitalTabs}
activeIndex={tabIndexs.order}
showNum={false}
/>
// ...
);
};
export default CategoryPage;
最终效果:
效果1:首页渲染,无抖动。
效果2:选项卡各种点击,URL会变化,且页面无抖动,可以用于收藏页面。
浅分析:
1、首页渲染,可以根据props.location.hash进行索引定位,在渲染阶段直接赋值。
2、标签点击跳转渲染,每个标签动态绑定onClick字段,并渲染生成合法的URL,再利用push去触发Effect(或者Updated),变化索引。
API设计:
1、循环绑定选项卡(需指定标识字段即变化的哈希的名称 type=news 'type’即名称)
/**
* 选项卡数组绑定onClick: push跳转事件
* @param {array} tabs 选项卡数组 (需要携带type标识作为hash名)
* @param {string} hash 当前URL
* @param {function} push history.push方法传入
* @param {string} name 覆写的哈希名称
* @param {string} pathname 跳转的前置路由 (如:'/user')
*/
export const tabArrBindClick = (tabs, hash, push, name, pathname) => {
// 将传入的hash键值化,并将
const hashInfo = hashToObj(hash);
// 遍历tabs绑定onClick字段
return tabs.map(t => ({
...t,
onClick: () => push(`${pathname}${objToHash({
...hashInfo,
[name]: t.type
})}`)
}))
}
2、哈希与对象相互转化
// 哈希串转对象
export const hashToObj = (hash) => {
let json = {};
if (hash.includes('#')) {
hash.substring(1).split('&').forEach((item, index) => {
const [field, val] = item.split('=');
json[field] = val;
});
}
return json;
}
// 对象转哈希串
export const objToHash = (obj) => {
let hash = '#';
Object.keys(obj).forEach(field => {
hash += `${field}=${obj[field]}&`;
});
return hash.slice(0, -1);
}
大神们可能会有更简洁的代码去实现,请轻喷小弟,但是基于单页应用,上述的思路应该是正确的。
最后,上述过程链较长,有时为了提高开发效率,且合适,也可以选择写死。