React:FrontendWiki前端维基项目&&用纯CSS实现树

15号的时候,来到了字节青训营进阶班,课程结题要完成一个项目

首先很幸运,我们的组长来自北大研院,从未与北大学子共事过,今日闻名不如人,真真实实感受到思维上的不一样之处,从思维角度就让我或多或少有些成长。

项目说明

FrontendWiki,如其名,是前维基,通过收集网络上大前端相关相关学习链接综合起来,形成方便用户进行前端学习的网站

项目技术框架

  • 前端在初期有考虑使用React+Ts+Sass的整体技术框架,不过最终定下来使用React+Css module
  • 后端采用SQL数据库+微服务的形式

What I do?

一棵树

真真切切的树,这是一颗从左向右生长的树

image.png

本来预估这里要用canvas或是svg实现,而且样式也不是这样。预估比较难,所以我和SH是一起接手了主页部分。 后来...后来,我通过递归的方式,再辅以css的巧妙应用,于是最终通过div+css完成了hhh

大致做法如下:

  • TreeNode:写一个节点组件,这里通过class的方式写组件
代码语言:javascript
复制
render(){
return(
<div className={styles.nodeContain}>
        <div>
          {this.props.data.content}
        </div>
      </div>
      )
   }

styles.nodeContain即为css moudle的基本使用方法 这里是一个基本框架,那么我们之后要逐步实现这个组件,现在先写树组件

  • Tree:预估逻辑会比较复杂,所以仍然使用class 那这个Tree怎么写?
  1. 首先我们应该确认一下数据结构,构建一个方便递归的数据,这部分数据理论上从云端调用,所以直接写一个专门获取数据的方法
代码语言:javascript
复制
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...),子节点列表
  1. 接下来就是通过递归生成树
代码语言:javascript
复制
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,后边也有地方需要修改

代码语言:javascript
复制
.flexRowNone {
  display: flex;
  flex-direction: row;
}
  1. 上一步我们实现了树,不过现在的树没有样式 我们回到TreeNode,给TreeNode确定样式,非常简单的实现
代码语言:javascript
复制
.nodeContain{
    position: relative;
    display: flex;
    margin-left: .6rem;
    margin-top: .9rem;
}

完成这一步你就会发现,成功生成了一颗树,当然它现在没有任何样式

  1. 锦上添花

我希望用户的点击可以在相关区域生成一定的点按反馈,这样可以有效提升用户反馈

代码语言:javascript
复制
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

代码语言:javascript
复制
.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;}
}

实现后效果

6.gif

光树是不行滴,现在需要处理点击树节点后的弹出

这一块的代码和Tree之间没有很大的关系,分开处理增强组件复用性

  • 预估实现效果
1.gif
  • 在最外层控制弹出
image.png

对应样式我就不放出来了,这里通过控制选中id来控制组件的展示

image.png
  • 其中左下角的小球实现相对简单,可以简单陈述,其他效果最好看我源代码

可以确定的是,我把它放到了最外层

代码语言:javascript
复制
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;

代码语言:javascript
复制
.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将它导出

于是在在其他地方点击后就可以通过以下方式控制动画效果,这一块其实可以再次封装

代码语言:javascript
复制
import {shareFunc} from '../Home.jsx';

onClick=()=>{
shareFunc.setParcel(true);
setTimeout(()=>{
shareFunc.setParcel(false);
},1500)
}

上边主要都是UI,现在则是数据块,通过redux获取并存储数据

这里简单说说点击树节点后要获取标签列表的示例

Reducer
代码语言:javascript
复制
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
代码语言:javascript
复制
/**

  • @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来确保每次数据都不会覆盖

结尾

其实项目难度不高,不过很有意义和价值,网络上确实缺少前端大汇总这种网站,希望每个前端都能找到自己的路