预览

image.png

页面结构

这是我的布局方式,也可根据你自己的喜好布局,效果如下图所示

啊啊啊小.png
html结构大概如下:

-Header
-Body
-SiderBar
-ContentBar
-ContentInner

  1. - **Brief**
  2. - **CodePreview**
  3. - **Api**

-Footer

看起来复杂,我们将网站分为两部分:一部分是静态的Layout,一部分是动态的Route,下面将重点介绍这两部分。
啊啊啊小.png
理一下思路:
黑色边框里的内容是静态的,也就是Layout部分,红色边框里的内容是动态的,也就是router-view。整个页面由Layout+router-view组成。
在src目录下新建site文件夹,存放整个网站。site下新建index.tsx作为你的页面的入口文件。猜也能猜到,它差不多长成这样。

  1. import Layout from './layout'
  2. import {Route} from 'react-router-dom'
  3. <Layout>
  4. <Route exact path='/button' component={ButtonPage} />
  5. <Route exact path='/icon' component={IconPage} />
  6. ......
  7. </Layout>

Layout

在整个页面中,Header,Footer,SideBar的内容是静态的,只有content会随着组件的切换发生变化。
site下新建layout文件夹 Header,Footer,SideBar 以及入口文件index.tsx。

-site
-layout
SiderBar.tsx
Header.tsx
Footer.tsx
index.tsx

Header.tsx

  1. import React from 'react';
  2. //这里的Icon现学现卖从ICON组件直接调用,可以先用png代替
  3. import Icon from "../../components/Icon"
  4. import logo from "../../logo.png"
  5. const Header:React.FC = ()=>{
  6. return (
  7. <header id="header">
  8. <div className="header-row">
  9. <a id="logoBox">
  10. <img className="logo_img" src={logo}/>
  11. <span>Sxd Design</span>
  12. </a>
  13. <div id="searchBox">
  14. <Icon icon="icon-search" style={{paddingRight:"16px"}}/>
  15. <input type="text" placeholder="搜索"/>
  16. </div>
  17. <div id="otherBox">
  18. <div className="version">Version : 0.0.1</div>
  19. <div className="git"> </div>
  20. </div>
  21. </div>
  22. </header>
  23. )
  24. }
  25. export default Header

Footer.tsx

  1. import React from 'react';
  2. const Footer:React.FC = ()=>{
  3. return (
  4. <div id="footer">
  5. <span>Made by frank</span>
  6. </div>
  7. )
  8. }
  9. export default Footer

SiderBar

-基础功能

  • 切换组件时,使url能切换到对应的路由。
  • 当前选中项的字体颜色要和未选中的区分开。 在点击组件时,为其添加额外样式。

-附加功能 (可选)

  • 屏幕过小时,收起菜单SiderBar。屏幕宽度足够时,展开菜单SideBar。

    页面布局 & 数据结构

    我封装组件的时候都是先把最基本的html结构搭起来,再根据html确定数据结构是什么样的。根据图上所示,看到有二级菜单两次循环肯定是逃不掉了。
    这里当初有个不合理的地方:我把路由数据在里又定义了一遍,每次新页面加进来要多改一个地方。就是因为当初测试的数据是在这里写的,结果后面每次都懒得改,我们发现错误要立马改正,长痛不如短痛。
    1. //页面布局
    2. <div className="sidebar">
    3. <ul>
    4. <li>
    5. <div>蔬菜组件</div>
    6. <div><a href="/qc">青菜</a></div>
    7. <div><a href="/qc">茄子</a></div>
    8. </li>
    9. <li>...</li>
    10. </ul>
    11. </div>
    12. //数据结构
    13. const sidebar = [
    14. {
    15. text:"蔬菜组件",
    16. children:[
    17. {
    18. id:1,
    19. text:"青菜",
    20. path:"/qc",
    21. },
    22. {
    23. id:2,
    24. text:"茄子",
    25. path:"/qz",
    26. }
    27. ]
    28. },
    29. {...}
    30. ]
    31. //最终
    32. <div className="sidebar">
    33. <ul>
    34. {sidebar.map(item=>{
    35. return (
    36. <li key={item.text}>
    37. <div>{item.text}</div>
    38. {item.children.map(item2=>{
    39. return (
    40. <div key={item2.id}>
    41. <a href={item2.path}>{item2.text}</a>
    42. </div>
    43. )
    44. })}
    45. </li>
    46. )
    47. })}
    48. </ul>
    49. </div>

    切换组件时,使url能切换到对应的路由。

    引入react-router-dom 4.x版本的
    用字符串的形式表示链接位置,创建可访问的导航。
    - to: string
    链接到的路径名或位置。
    1. <Link to="/About"/>
    - to: object
    要链接的位置。
    1. <Link to={{
    2. pathname: '/courses',
    3. search: '?sort=name',
    4. hash: '#the-hash',
    5. state: { fromDashboard: true }
    6. }}/>
    - replace: bool
    如果为true,单击链接将替换历史堆栈中的当前条目,而不是添加新条目。
    1. <Link to="/About" replace />
    把a标签替换成link组件改造一下,需求完成!SiderBar关键代码如下。
    1. interface ISideData{
    2. text:string,
    3. children:{
    4. id:number,
    5. text:string,
    6. path:string,
    7. }[]
    8. }
    9. const sidebar:ISideData[]=[
    10. {
    11. text:"通用",
    12. children:[
    13. {
    14. id:1,
    15. text:"button 按钮",
    16. path:"/button",
    17. },
    18. {
    19. id:2,
    20. text:"Icon 图标",
    21. path:"/icon",
    22. },
    23. ]
    24. }
    25. ]
    26. <div className="sidebar">
    27. <ul className="firstUl">
    28. {sidebar.map((item,index)=>{
    29. return (
    30. <li className="firstLi" key={index}>
    31. <div className="firstTit">{item.text}</div>
    32. {item.children.map(item2=>{
    33. return (
    34. <div key={item2.id}>
    35. <Link className="thirdTit" to={item2.path}>
    36. {item2.text}
    37. </Link>
    38. </div>
    39. )
    40. })}
    41. </li>
    42. )
    43. })}
    44. </ul>
    45. </div>


    为当前文字添加active样式。

    一开始还在使用Link组件,没有引入NavLink这个组件。手动地判断active的逻辑。我在组件里监听url的变化,根据当前url和path做对比,相同则为当前菜单添加active类。比较麻烦,实现起来很不优雅。下面先看一下react-router-dom 4.x版本的的介绍。

    一种特殊版本的,给定链接的位置与当前URL匹配时,将向渲染元素添加样式或者类。
    - activeClassName: string
    与当前URL匹配时,给元素添加的类。默认值是 active。如果设置了className,会与其合并。
    - activeStyle: object
    与当前URL匹配时,应用于元素的样式。
    - exact: bool
    当为true时,仅当位置匹配完全时才会应用class/style。
    - isActive: func
    增加用于确定链接是否活动的额外逻辑的功能。如果您想要更多地验证链接的路径名与当前URL的路径名匹配,可以使用它。

它能完美地解决我们的需求,我使用默认类名——active,等会要在样式中编写 .active 的样式,所有layout样式在此小节的最后。SiderBar完整代码如下

  1. import React from 'react';
  2. import { ISideProps,ISideData } from '../interface/sideBar'
  3. import { NavLink } from 'react-router-dom'
  4. const SideBar:React.FC<ISideProps> = (props:ISideProps)=>{
  5. const sidebar:ISideData[]=[
  6. {
  7. text:"通用",
  8. children:[
  9. {
  10. id:1,
  11. text:"button 按钮",
  12. path:"/button"
  13. },
  14. {
  15. id:2,
  16. text:"Icon 图标",
  17. path:"/icon"
  18. },
  19. ]
  20. },
  21. ]
  22. return (
  23. <div className="sidebar" style={props.style}>
  24. <ul className="firstUl">
  25. {sidebar.map((item,index)=>{
  26. return (
  27. <li className="firstLi" key={index}>
  28. <div className="firstTit">{item.text}</div>
  29. {item.children.map(item2=>{
  30. return (
  31. <div key={item2.id}>
  32. <NavLink className="thirdTit" to={item2.path}>
  33. {item2.text}
  34. </NavLink>
  35. </div>
  36. )
  37. })}
  38. </li>
  39. )
  40. })}
  41. </ul>
  42. </div>
  43. )
  44. }
  45. //插播一条样式
  46. .sidebar .active{
  47. color:#7763e9 !important
  48. }

展开收起菜单SiderBar

SiderBar需要一个参数控制收起展开状态,这个状态放在layout/index.tsx中统一管理。

index.tsx

这里需要监听整个屏幕的宽度,小于等于750px时收起SiderBar,同时取消菜单和内容的边距,边距是在内容ContentBar设置的的padding-left。大于750px展开SiderBar,同样地,增加菜单和内容的边距。
我把监听屏幕宽度封装成了一个hook,小于750输出false,大于等于750输出true,你在哪都可以使用它。

  1. import React,{useState} from 'react';
  2. import { connect } from 'react-redux';
  3. import useScreenWidth from './hooks/useScreenWidth'
  4. import Header from './header'
  5. import SiderBar from './SiderBar'
  6. import Footer from './footer'
  7. import {ISiteProps,ISiteState} from '../interface/site'
  8. const Site:React.FC<ISiteProps> = (props:ISiteProps)=>{
  9. const { children } = props
  10. //监听屏幕宽度的hook。小于750输出false,大于等于750输出true
  11. const showSiderBar = useScreenWidth()
  12. const hide = {
  13. display:"none",
  14. padding:"50px 0 50px 0"
  15. }
  16. const show = {
  17. display:"block",
  18. padding:"50px 0 50px 20%"
  19. }
  20. //true就用show样式,false就用hide样式
  21. const dynStyle = showSiderBar?show:hide
  22. return (
  23. <div className="wrapper">
  24. <Header/>
  25. <div className="main container">
  26. <SiderBar style={{display:dynStyle.display}}/>
  27. <div className="contentbar" style={{padding:dynStyle.padding}}>
  28. {children}
  29. <Footer/>
  30. </div>
  31. </div>
  32. </div>
  33. )
  34. }
  35. export default Site

useScreenWidth.tsx 完整代码如下
有一个点需要注意就是,如果添加了监听事件,一定要在不需要的时候取消监听,不然大大影响浏览器性能!!!

  1. import {useState,useEffect} from 'react'
  2. const useScreenWidth = ():any=>{
  3. const [width,setWidth] = useState(window.innerWidth)
  4. const [moreThan750,isMoreThan750] = useState(true)
  5. /* screen size change */
  6. const resizeHandle = ()=>{
  7. removeEventListener()
  8. setWidth(window.innerWidth)
  9. }
  10. const addEventListener=()=>{
  11. window.addEventListener("resize",resizeHandle,false);
  12. }
  13. const removeEventListener=()=>{
  14. window.removeEventListener("resize",resizeHandle);
  15. }
  16. addEventListener()
  17. /* end */
  18. useEffect(()=>{
  19. if(width<=750){
  20. isMoreThan750(false)
  21. }else{
  22. isMoreThan750(true)
  23. }
  24. },[width])
  25. return moreThan750
  26. }
  27. export default useScreenWidth

最后样式文件 style.scss

  1. h1,h2,h3,h4{
  2. margin:0;
  3. padding:0
  4. }
  5. a{
  6. text-decoration: none;
  7. outline:none;
  8. border:none
  9. }
  10. a:-webkit-any-link:focus {
  11. outline:none !important;
  12. outline-offset: 0px !important;
  13. }
  14. a:focus {
  15. outline:none !important;
  16. outline-offset: 0px !important;
  17. }
  18. .container{
  19. margin:0 auto;
  20. }
  21. #header{
  22. position: sticky;
  23. top:0;
  24. z-index: 10;
  25. max-width: 100%;
  26. background: #fff;
  27. box-shadow: 0 2px 8px #f0f1f2;
  28. .header-row{
  29. display: flex;
  30. align-items: center;
  31. height: 64px;
  32. #logoBox{
  33. padding-left: 40px;
  34. overflow: hidden;
  35. color: rgba(0,0,0,0.85);
  36. font-size: 18px;
  37. font-weight: 500;
  38. width:25%;
  39. .logo_img{
  40. height: 32px;
  41. width:32px;
  42. margin-right: 16px;
  43. }
  44. }
  45. #searchBox{
  46. width:30%;
  47. position: relative;
  48. display: flex;
  49. flex: auto !important;
  50. align-items: center;
  51. height: 22px;
  52. margin: 0 auto 0 0 !important;
  53. padding-left: 16px;
  54. line-height: 22px;
  55. white-space: nowrap;
  56. border-left: 1px solid #f0f0f0;
  57. input{
  58. background: transparent;
  59. border: 0;
  60. box-shadow: none;
  61. outline: none;
  62. }
  63. input:focus{
  64. background: transparent;
  65. border: 0;
  66. outline: none;
  67. box-shadow: none;
  68. }
  69. }
  70. #otherBox{
  71. width:45%;
  72. display: flex;
  73. align-items: center;
  74. .version{
  75. display: inline-block;
  76. height: 20px;
  77. line-height: 20px;
  78. margin-right:16px;
  79. }
  80. .git{
  81. display: inline-block;
  82. width: 20px;
  83. height: 20px;
  84. margin: 0;
  85. background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPjxwYXRoIGZpbGw9IiMzMzMiIGQ9Ik0zMiAxMy40Yy0xMC41IDAtMTkgOC41LTE5IDE5IDAgOC40IDUuNSAxNS41IDEzIDE4IDEgLjIgMS4zLS40IDEuMy0uOXYtMy4yYy01LjMgMS4xLTYuNC0yLjYtNi40LTIuNi0uOS0yLjEtMi4xLTIuNy0yLjEtMi43LTEuNy0xLjIuMS0xLjEuMS0xLjEgMS45LjEgMi45IDIgMi45IDIgMS43IDIuOSA0LjUgMi4xIDUuNSAxLjYuMi0xLjIuNy0yLjEgMS4yLTIuNi00LjItLjUtOC43LTIuMS04LjctOS40IDAtMi4xLjctMy43IDItNS4xLS4yLS41LS44LTIuNC4yLTUgMCAwIDEuNi0uNSA1LjIgMiAxLjUtLjQgMy4xLS43IDQuOC0uNyAxLjYgMCAzLjMuMiA0LjcuNyAzLjYtMi40IDUuMi0yIDUuMi0yIDEgMi42LjQgNC42LjIgNSAxLjIgMS4zIDIgMyAyIDUuMSAwIDcuMy00LjUgOC45LTguNyA5LjQuNy42IDEuMyAxLjcgMS4zIDMuNXY1LjJjMCAuNS40IDEuMSAxLjMuOSA3LjUtMi42IDEzLTkuNyAxMy0xOC4xIDAtMTAuNS04LjUtMTktMTktMTl6Ii8+PC9zdmc+);
  86. background-size: 100% 100%;
  87. background-repeat: no-repeat;
  88. }
  89. }
  90. }
  91. }
  92. .sxdTitle{
  93. height:100%;
  94. display: flex;
  95. align-items: center;
  96. justify-content: space-between;
  97. border-bottom:1px solid #7763e9;
  98. }
  99. .sxdTitle h1{
  100. color:#7763e9;
  101. text-align: left;
  102. font-size: 24px;
  103. font-weight: bold;
  104. margin:0;
  105. }
  106. .main{
  107. display: flex;
  108. align-content: center;
  109. }
  110. .sidebar{
  111. padding-left:8px;
  112. width:20%;
  113. border-right:1px solid #f0f0f0;
  114. position: fixed;
  115. top:80px;
  116. height: 100%;
  117. overflow: hidden;
  118. }
  119. .sidebar:hover {
  120. overflow-y: auto;
  121. }
  122. .sidebar .active, .thirdTit:hover{
  123. color:#7763e9 !important
  124. }
  125. .sidebar .firstUl{
  126. padding:0 0 190px 0;
  127. list-style-type:none;
  128. margin:0;
  129. }
  130. .firstLi{
  131. padding:0 16px 0 40px
  132. }
  133. .sidebar .firstTit{
  134. font-size: 13px;
  135. color: rgba(0,0,0,.45);
  136. line-height: 40px;
  137. height: 40px;
  138. margin: 0;
  139. padding: 0;
  140. text-decoration: none;
  141. display: block;
  142. position: relative;
  143. transition: .15s ease-out;
  144. margin-top: 14px;
  145. margin-bottom: 14px;
  146. }
  147. .sidebar .firstTit:after{
  148. position: relative;
  149. display: block;
  150. width: calc(100% - 20px);
  151. height: 1px;
  152. background: #f0f0f0;
  153. content: "";
  154. }
  155. .sidebar .thirdTit{
  156. display: block;
  157. height: 40px;
  158. color: rgba(0,0,0,.85);
  159. line-height: 40px;
  160. font-size: 14px;
  161. overflow: hidden;
  162. white-space: nowrap;
  163. text-overflow: ellipsis;
  164. font-weight: 400;
  165. }
  166. .sidebar .thirdTit:hover{
  167. text-decoration: none;
  168. }
  169. .codeSize{
  170. font-size: 16px;
  171. }
  172. .contentbar{
  173. width:100%;
  174. padding:50px 0 50px 20%;
  175. .contentInner{
  176. padding: 0 0px 32px 64px;
  177. width:100%;
  178. .brief{
  179. font-size: 14px;
  180. line-height: 2;
  181. h1{
  182. margin-top: 8px;
  183. margin-bottom: 20px;
  184. color: rgba(0,0,0,.85);
  185. font-weight: 500;
  186. font-size: 30px;
  187. line-height: 38px;
  188. }
  189. h2{
  190. font-size: 24px;
  191. line-height: 32px;
  192. clear: both;
  193. margin: 1.6em 0 .6em;
  194. color: rgba(0,0,0,.85);
  195. font-weight: 500;
  196. }
  197. p{
  198. margin:1em 0
  199. }
  200. li{
  201. list-style-type: circle;
  202. }
  203. li p{
  204. margin: .2em 0;
  205. }
  206. }
  207. .codeWrap{
  208. padding-right:16px;
  209. display: inline-block;
  210. width: calc(50% - 32px);
  211. vertical-align: top;
  212. }
  213. .codeBox{
  214. border: 1px solid #f0f0f0;
  215. border-radius: 2px;
  216. margin: 0 0 16px;
  217. .demo{
  218. padding: 42px 24px 50px;
  219. color: rgba(0,0,0,.85);
  220. border-bottom: 1px solid #f0f0f0;
  221. }
  222. .markdown{
  223. width: 100%;
  224. font-size: 14px;
  225. border-radius: 0 0 2px 2px;
  226. position: relative;
  227. .title{
  228. position: absolute;
  229. top: -13px;
  230. margin-left: 16px;
  231. padding: 1px 8px;
  232. color: rgba(0,0,0,.85);
  233. background: #fff;
  234. }
  235. .description{
  236. padding: 18px 24px 12px;
  237. }
  238. .actions{
  239. display: flex;
  240. align-items: center;
  241. justify-content: center;
  242. padding: 12px 0;
  243. border-top: 1px dashed #f0f0f0;
  244. }
  245. }
  246. .code{
  247. border-top: 1px dashed #f0f0f0;
  248. padding: 16px 32px;
  249. color: rgba(0,0,0,.85);
  250. font-size: 14px;
  251. line-height: 2;
  252. background: #fff;
  253. }
  254. }
  255. }
  256. }
  257. #footer{
  258. border-top:1px solid #f0f0f0;
  259. background-color:black;
  260. color:gray;
  261. margin: 0 auto;
  262. padding: 16px 0;
  263. text-align: center;
  264. span{
  265. font-size: 16px;
  266. line-height: 32px;
  267. margin:0
  268. }
  269. }
  270. /* transition */
  271. .star-enter {
  272. opacity: 0;
  273. transform: translateX(100px);
  274. }
  275. .star-enter-active {
  276. opacity: 1;
  277. transform: translateX(0px);
  278. transition: all 700ms
  279. }
  280. .star-exit {
  281. opacity: 1;
  282. transform: translateX(-100px);
  283. }
  284. .star-exit-active {
  285. opacity: 0;
  286. transform: translateX(0px);
  287. transition: all 700ms
  288. }

路由部分 ContentBar

在这里我意识到了在siderBar中去定义路由数据造成了冗余,思路不够清晰需要反思优化,应该定义路由表文件统一存取(第二个版本优化的)。在index.js用当前的url作为的key优化了过渡效果。

Router目录

-router
index.js
routes.js //路由表

routes.js

  1. import ButtonPage from '../pages/Button'
  2. import IconPage from '../pages/Icon'
  3. export default [
  4. {
  5. text:"通用",
  6. children:[
  7. {
  8. id:1,
  9. text:"button 按钮",
  10. path:"/button",
  11. component:ButtonPage
  12. },
  13. {
  14. id:2,
  15. text:"Icon 图标",
  16. path:"/icon",
  17. component:IconPage
  18. },
  19. ]
  20. }
  21. ]

路由切换时,我们希望右边的content出现和隐藏能有一个动画效果。 故引入第三方组件 react-transition-group。CSSTransition的key属性是过渡生效的关键,useLocation()可以获取当前url的信息,key会随着url的变化而变化,满足我们的需求。参考了这篇,我没有用withroute包裹起来使其获得location,而是直接使用useLocation()获取。

index.js

  1. import {Switch,Route,Redirect,useLocation} from 'react-router-dom'
  2. import sidebar from './routes'
  3. import { TransitionGroup, CSSTransition } from 'react-transition-group'
  4. function AppRouter() {
  5. let location = useLocation()
  6. return (
  7. <TransitionGroup className={'router-wrapper'}>
  8. <CSSTransition timeout={700} classNames="star" key={location.pathname}>
  9. <Switch>
  10. { sidebar.map((item)=>{
  11. return item.children.map(item2=>{
  12. return ( <Route exact path={item2.path} component={item2.component}/> )
  13. })
  14. }) }
  15. <Redirect to="button"/>
  16. </Switch>
  17. </CSSTransition>
  18. </TransitionGroup>
  19. );
  20. }
  21. export default AppRouter;

过渡样式

  1. /* transition */
  2. .star-enter {
  3. opacity: 0;
  4. transform: translateX(100px);
  5. }
  6. .star-enter-active {
  7. opacity: 1;
  8. transform: translateX(0px);
  9. transition: all 700ms
  10. }
  11. .star-exit {
  12. opacity: 1;
  13. transform: translateX(-100px);
  14. }
  15. .star-exit-active {
  16. opacity: 0;
  17. transform: translateX(0px);
  18. transition: all 700ms
  19. }

路由已经完成了他该完成的任务,要添加组件的话只需要引入该组件并在路由表中添加一条数据即可。

Components目录

-components
-Content
demoList.tsx
index.tsx
-demo
Code.tsx
Preview.tsx
Introduce.tsx
index.tsx

结构如图
demolist.png
我们要开始封装page页面的组件。ButtonPage里定义好参数直接调用即可。数据参考如下

  1. import Document from '../components/content'
  2. import React from 'react'
  3. const ButtonPage:React.FC = ()=>{
  4. const document:Iprops = {
  5. brief:(
  6. <>
  7. <h1>Button</h1>
  8. <p>Button Brief</p>
  9. <h2>代码演示</h2>
  10. </>
  11. ),
  12. content:[
  13. {
  14. id:1,
  15. demo:<div>//demo here</div>,
  16. title:"this is title",
  17. description:"this is description",
  18. code:`code here`
  19. },
  20. {...}
  21. ],
  22. api:(
  23. <div>
  24. <h2>Button 参数</h2>
  25. </div>
  26. )
  27. }
  28. return (<Document document={document}/>)
  29. }
  30. export default ButtonPage

components下index.tsx是入口文件,接收一个参数document。document有三个对象brief、content数组、api。分别是简介、演示区、底部api接口。

Content

入口文件

接收document参数,简介和API没有封装成组件直接调用,只有演示区域封装成demoList组件。

  1. import React from 'react';
  2. import DemoList from './demoList'
  3. import {IDoc} from '../../interface/document'
  4. const Document:React.FC<IDoc> = (props:IDoc)=>{
  5. const {document} = props
  6. return (
  7. <div className="contentInner">
  8. <section className="brief">
  9. {document.brief}
  10. </section>
  11. <DemoList content={document.content}/>
  12. <section className="brief">
  13. {document.api}
  14. </section>
  15. </div>
  16. )
  17. }
  18. export default Document

DemoList.tsx

接收一个数组content,代表所有的demoList。
demoList 长度要是小于1就不展示。再来考虑长度大于大于1的情况:由于 demoList 左右分布,我做了一个伪瀑布流,定义两个空数组,长度一半的内容给左边,长度另一半的内容给右边。

  1. import React from 'react';
  2. import Demo from './demo'
  3. import { IDemo } from './interface'
  4. interface IDemoList{
  5. content:IDemo[];
  6. }
  7. const DemoList:React.FC<IDemoList> = (props:IDemoList)=>{
  8. const {content}=props
  9. const cLength = content.length
  10. if(cLength<1) return (<div></div>)
  11. let left:IDemo[]=[];
  12. let right:IDemo[]=[];
  13. const middle = cLength % 2 ==0 ? cLength/2 : Math.floor(cLength/2) + 1
  14. for(let i=0;i<middle;i++){
  15. left=[...left,content[i]]
  16. }
  17. for(let i=0;i<cLength-middle;i++){
  18. right=[...right,content[i+middle]]
  19. }
  20. return (
  21. <div>
  22. <div className="codeWrap">
  23. {left.map((item:IDemo)=>{
  24. return (<Demo key={item.id} {...item}/>)
  25. })}
  26. </div>
  27. <div className="codeWrap" style={{paddingRight:"0px"}}>
  28. {right.map((item:IDemo)=>{
  29. return (<Demo key={item.id} {...item}/>)
  30. })}
  31. </div>
  32. </div>
  33. )
  34. }

这里需要用到demo下的入口文件,一个demo我把它分成了三部分都比较简单,我一部分一部分说。

Demo

Preview.tsx

接收一个参数demo
image.png

  1. import React from 'react';
  2. import { IPreview } from "../interface";
  3. const Preview:React.FC<IPreview> = (props:IPreview)=>{
  4. const { demo } = props
  5. return (
  6. <section className="demo">
  7. {demo}
  8. </section>
  9. )
  10. }
  11. export default Preview

Introduce.tsx

image.png
接收两个字符串参数,展示标题和描述title, description,接收一个函数switch_code_display,切换代码的展开收起

  1. import React from 'react';
  2. import Icon from "../../../../components/Icon"
  3. import { IIntroduce } from "../interface";
  4. const Introduce:React.FC<IIntroduce> = (props:IIntroduce)=>{
  5. const { title, description, switch_code_display } = props
  6. return (
  7. <section className="markdown">
  8. <div className="title">
  9. <span>{title}</span>
  10. </div>
  11. <div className="description">
  12. <p>{description}</p>
  13. </div>
  14. <div className="actions">
  15. <Icon icon="icon-file" style={{fontSize:"16px",lineHeight:"16px",cursor:"pointer"}}/>
  16. <Icon icon="icon-code" onClick={()=>switch_code_display()} style={{fontSize:"16px",lineHeight:"16px",cursor:"pointer",marginLeft:"16px"}}/>
  17. </div>
  18. </section>
  19. )
  20. }
  21. export default Introduce

Code.tsx

image.png
接收两个参数,一个展示代码code,一个管理收起和展开的状态hideStyle
引入 react-syntax-highlighter 代码高亮组件
样式无需安装,可以直接从react-syntax-highlighter/dist/esm/styles/hljs引入进来,这里使用 a11yLight,语言选择javascript

  1. import SyntaxHighlighter from 'react-syntax-highlighter';
  2. import { a11yLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
  3. import React from 'react';
  4. import { ICode } from "../interface";
  5. const Code:React.FC<ICode> = (props:ICode)=>{
  6. const { code, hideStyle } =props
  7. return (
  8. <section className="code" style={hideStyle}>
  9. <SyntaxHighlighter language="javascript" style={a11yLight} codeTagProps={{className:"codeSize"}}>
  10. {code}
  11. </SyntaxHighlighter>
  12. </section>
  13. )
  14. }
  15. export default Code

index.tsx

像乐高玩具一样把所有组件拼起来,加入切换代码展示隐藏的状态,分发给需要的组件。

  1. import React,{useState} from 'react';
  2. import { IDemo } from '../interface'
  3. import Code from './Code';
  4. import Preview from './Preview';
  5. import Introduce from './Introduce';
  6. const Demo:React.FC<IDemo> = (props:IDemo)=>{
  7. const { demo, title="", description="", code="" } = props
  8. const [hideStyle,setHideStyle] = useState({display:"none"})
  9. const switch_code_display = ()=>{
  10. if(hideStyle.display==="none"){
  11. setHideStyle({display:"block"})
  12. }else{
  13. setHideStyle({display:"none"})
  14. }
  15. }
  16. return (
  17. <div className="codeBox">
  18. <Preview demo={demo}/>
  19. <Introduce title={title} description={description} switch_code_display={switch_code_display}/>
  20. <Code code={code} hideStyle={hideStyle}/>
  21. </div>
  22. )
  23. }
  24. export default Demo

Pages

新建pages文件夹
-site
-pages
ButtonPage.tsx
IconPage.tsx
……
组件都写好了,现在只要把数据定义好,传给他们就行了,举一个写好的ButtonPage的例子。

  1. import Document from '../components/content'
  2. import React from 'react'
  3. import { Icontent, Iprops} from '../interface/document'
  4. import Button from '../../components/Button/button'
  5. const ButtonPage:React.FC = ()=>{
  6. const brief=(<>
  7. <h1>Button按钮</h1>
  8. <p>按钮用于开始一个即时操作</p>
  9. <h2>何时使用</h2>
  10. <div>
  11. <p>标记了操作命令,响应用户点击行为,触发相应的业务逻辑。</p>
  12. <ul>
  13. <li>
  14. <p>主按钮:用于主行动点,一个操作区域只能有一个主按钮。</p>
  15. </li>
  16. <li>
  17. <p>默认按钮:用于没有主次之分的一组行动点。</p>
  18. </li>
  19. <li>
  20. <p>文本按钮:用于最次级的行动点。</p>
  21. </li>
  22. <li>
  23. <p>链接按钮:用于作为外链的行动点。</p>
  24. </li>
  25. </ul>
  26. </div>
  27. <h2>代码演示</h2>
  28. </>)
  29. const items:Icontent[]=[
  30. {
  31. id:1,
  32. demo:<div>
  33. <Button type="primary" style={{marginRight:"20px"}}>primary</Button>
  34. <Button type="default" style={{marginRight:"20px"}}>default</Button>
  35. <Button type="text" style={{marginRight:"20px"}}>text</Button>
  36. <Button type="link">link</Button>
  37. </div>,
  38. title:"按钮类型",
  39. description:"按钮有四种类型:主按钮、次按钮、文本按钮和链接按钮。主按钮在同一个操作区域最多出现一次。",
  40. code:
  41. `
  42. import { Button } from 'sxdui'
  43. <div>
  44. <Button type="primary" style={{marginRight:"20px"}}>primary</Button>
  45. <Button type="default" style={{marginRight:"20px"}}>default</Button>
  46. <Button type="text" style={{marginRight:"20px"}}>text</Button>
  47. <Button type="link">link</Button>
  48. </div>
  49. `
  50. },
  51. {
  52. id:2,
  53. demo:<div>
  54. <Button type="primary" size="large" style={{marginRight:"20px"}}>large</Button>
  55. <Button type="default" style={{marginRight:"20px"}}>middle</Button>
  56. <Button type="text" size="small" style={{marginRight:"20px"}}>small</Button>
  57. <div style={{marginTop:"16px"}}/>
  58. <Button type="primary" size="large" shape="round" style={{marginRight:"20px"}}>large round</Button>
  59. <Button type="default" shape="round" style={{marginRight:"20px"}}>middle round</Button>
  60. <Button type="text" shape="round" size="small" style={{marginRight:"20px"}}>small round</Button>
  61. </div>,
  62. title:"按钮尺寸",
  63. description:"通过设置 size 为 large small 分别把按钮设为大、小尺寸。若不设置 size,则尺寸为中",
  64. code:
  65. `
  66. import { Button } from 'sxdui'
  67. <div>
  68. <Button type="primary" size="large" style={{marginRight:"20px"}}>large</Button>
  69. <Button type="default" style={{marginRight:"20px"}}>middle</Button>
  70. <Button type="text" size="small" style={{marginRight:"20px"}}>small</Button>
  71. <div style={{marginTop:"16px"}}/>
  72. <Button type="primary" size="large" shape="round" style={{marginRight:"20px"}}>large round</Button>
  73. <Button type="default" shape="round" style={{marginRight:"20px"}}>middle round</Button>
  74. <Button type="text" shape="round" size="small" style={{marginRight:"20px"}}>small round</Button>
  75. </div>
  76. `
  77. }
  78. ]
  79. const api =
  80. <div>
  81. <h2>Button 参数</h2>
  82. </div>
  83. const document:Iprops = {
  84. brief:brief,
  85. content:items,
  86. api:api
  87. }
  88. return (<Document document={document}/>)
  89. }
  90. export default ButtonPage

先把site的入口文件的坑填上,最后在项目的入口文件引入Site即可

  1. // site/index.tsx
  2. import React from 'react';
  3. import AppRouter from './router'
  4. import Layout from './layout'
  5. const Site:React.FC = (props)=>{
  6. return (
  7. <div className="App" style={{overflow:"hidden"}}>
  8. <Layout>
  9. <AppRouter />
  10. </Layout>
  11. </div>
  12. )
  13. }
  14. export default Site

结束

接下来,接下来就可以在pages下新建文件,开始封装组件啦!
封装一个Select组件