15号的时候,来到了字节青训营进阶班,课程结题要完成一个项目
首先很幸运,我们的组长来自北大研院,从未与北大学子共事过,今日闻名不如人,真真实实感受到思维上的不一样之处,从思维角度就让我或多或少有些成长。
项目说明
FrontendWiki,如其名,是前维基,通过收集网络上大前端相关相关学习链接综合起来,形成方便用户进行前端学习的网站
项目技术框架
- 前端在初期有考虑使用React+Ts+Sass的整体技术框架,不过最终定下来使用React+Css module
- 后端采用SQL数据库+微服务的形式
What I do?
一棵树
真真切切的树,这是一颗从左向右生长的树
本来预估这里要用canvas或是svg实现,而且样式也不是这样。预估比较难,所以我和SH是一起接手了主页部分。 后来...后来,我通过递归的方式,再辅以css的巧妙应用,于是最终通过div+css完成了hhh
大致做法如下:
- TreeNode:写一个节点组件,这里通过class的方式写组件
render(){
return(
<div className={styles.nodeContain}>
<div>
{this.props.data.content}
</div>
</div>
)
}
styles.nodeContain即为css moudle的基本使用方法 这里是一个基本框架,那么我们之后要逐步实现这个组件,现在先写树组件
- Tree:预估逻辑会比较复杂,所以仍然使用class 那这个Tree怎么写?
- 首先我们应该确认一下数据结构,构建一个方便递归的数据,这部分数据理论上从云端调用,所以直接写一个专门获取数据的方法
getTreeData() {
axios
.get("xxx")
.then((res) => {
this.setState({
data: res,
});
})
.catch((e) => {
this.setState({
data: [
{
content: "CSS",
id: "1",
level: 0,
childrens: [
{content: "动画属性",id: "1-1",level: 1,
childrens: [
{content: "transition",id: "1-1-1",level: 2,childrens: [],},
{content: "animation",id: "1-1-2",level: 2,childrens: [],},
{content: "贝塞尔函数",id: "1-1-3",level: 2,childrens: [],},
],
},
{ content: "选择器", id: "1-2", level: 1, childrens: [] },
],
},
{content: "HTML",id: "2",level: 0,
childrens: [
{content: "标签集",id: "2-1",level: 1,
childrens: [
{ content: "div", id: "2-1-1", level: 2, childrens: [] },
],
},
{ content: "规范", id: "2-2", level: 1, childrens: [] },
],
},
{ content: "React", id: "3", level: 0, childrens: [] },
{ content: "JS", id: "4", level: 0, childrens: [] },
],
});
});
}
这个结构可以说是非常清晰了,每个节点包含这些数据
- content,代表显示的内容,如CSS
- id,用来作为key的参数
- level,代表重要程度,在树中会有样式的差异
- childrens(语义上这里不应该有s...),子节点列表
- 接下来就是通过递归生成树
getNode(data) {
return data.map((item) => {
return (
<div key={item.id} className="flexRowNone">
<TreeNode
data={item}
/>
<div>{this.getNode(item.childrens)}</div>
</div>
);
});
}
这里即为简单的递归生成,其中flexRowNone的样式如下,简单的flex+row,确定树是向右生长,如果改竖直的话这里用column,后边也有地方需要修改
.flexRowNone {
display: flex;
flex-direction: row;
}
- 上一步我们实现了树,不过现在的树没有样式 我们回到TreeNode,给TreeNode确定样式,非常简单的实现
.nodeContain{
position: relative;
display: flex;
margin-left: .6rem;
margin-top: .9rem;
}
完成这一步你就会发现,成功生成了一颗树,当然它现在没有任何样式
- 锦上添花
我希望用户的点击可以在相关区域生成一定的点按反馈,这样可以有效提升用户反馈
class TreeNode extends React.Component {
constructor(props) {
super(props);
this.state = {
point: false,
top: 0,
left: 0,
};
}
pointShow(e) {
let { layerX, layerY, offsetX, offsetY } = e.nativeEvent;
this.setState({
top: layerY || offsetY,
left: layerX || offsetX,
point: true,
});
setTimeout(() => {
this.setState({
point: false,
});
}, 600);
}
render() {
return (
<div className={styles.nodeContain}>
<div
onClick={(e) => {
this.pointShow(e);
}}
style={styleMap[this.props.data.level]}
>
{this.state.point ? (
<div
className={styles.pointCircle}
style={{ top: this.state.top, left: this.state.left }}
></div>
) : (
<></>
)}
{this.props.data.content}
</div>
</div>
);
}
}
上边的 {this.state.point ? ( <div className={styles.pointCircle} style={{ top: this.state.top, left: this.state.left }} ></div> ) : ( <></> )}
实际上就是点按反馈的核心代码;下边则是相关动态的css
.pointCircle{
border: solid .6px rgba(20, 20, 20, 0.24);
width: .1px;
height: .1px;
position: absolute;
border-radius: 100%;
animation: showPonitCircle .6s ease;
z-index: 10;
}
@keyframes showPonitCircle{
to{transform: scale(100);opacity: 0;}
}
实现后效果
光树是不行滴,现在需要处理点击树节点后的弹出
这一块的代码和Tree之间没有很大的关系,分开处理增强组件复用性
- 预估实现效果
- 在最外层控制弹出
对应样式我就不放出来了,这里通过控制选中id来控制组件的展示
- 其中左下角的小球实现相对简单,可以简单陈述,其他效果最好看我源代码
可以确定的是,我把它放到了最外层
export var shareFunc={
}
function Home() {
const [headShow,setHeadShow]=useState(true);
const [showParcel, setParcel] = useState(false);
shareFunc.setParcel=setParcel;
return (
<>
{showParcel ? <div className={styles.parcel}></div> : <></>}
{
headShow?<HomeHead />:<></>
}
<HomeMain setHeadShow={setHeadShow}/>
</>
);
}
export default Home;
.parcel{
position: fixed;
height: 100%;
border-radius: 100%;
border: solid .6px #000;
width: 100%;
width: .1px;
height: .1px;
bottom: 0;
left: 0;
z-index: 100000;
animation: parcelShow ease 1s forwards;
}
@keyframes parcelShow {
50%{transform: scale(3000);opacity: 1;}
100%{transform: none;opacity: 0;}
}
复制代码
这里通过setParcel控制小球的显隐,为了方便各个地方调用, 我通过shareFunc将它导出
于是在在其他地方点击后就可以通过以下方式控制动画效果,这一块其实可以再次封装
import {shareFunc} from '../Home.jsx';
onClick=()=>{
shareFunc.setParcel(true);
setTimeout(()=>{
shareFunc.setParcel(false);
},1500)
}
上边主要都是UI,现在则是数据块,通过redux获取并存储数据
这里简单说说点击树节点后要获取标签列表的示例
Reducer
const initState = {
linkList:[]
};
const linkReducer = (state = initState, action) => {
console.log('state',state)
switch (action.type) {
case "getLinkListStart":
case "getLinkListSuccess":
case "getLinkListFail":
state.linkList={...state.linkList, ...action.payload};
return {...state};
default:
return state;
}
};
export default linkReducer;
Reducer在redux中提供对于Action的处理,具体可以看看下边的action
Action
/**
- @author source
- @updateTime 2021/8/20 16:00
- 通过词条id获得词条下的链接列表;
- @param term {String}
非必填
*/
const getLinkListAction = (term) => (dispatch) => {
getLinksbyTerm(term)
.then((res) => {
dispatch({
type: "getLinkListSuccess",
payload: {
[${term}
]: {
data: { ...res },
code: 1,
},
},
});
})
.catch((e) => {
dispatch({
type: "getLinkListFail",
payload: {
[${term}
]: {
data: { ...e },
code: -1,
},
},
});
console.log("store", store.getState().linkReducer);
});
};
综合Reducer和Action你就会知道,我们触发Action后,Reducer会接收到数据,通过type来处理对应数据更新到Store中,这里通过动态的key来确保每次数据都不会覆盖
结尾
其实项目难度不高,不过很有意义和价值,网络上确实缺少前端大汇总这种网站,希望每个前端都能找到自己的路