Vue3/React-对比实现仿知乎专栏–01
技术栈
Vue3:全家桶+TS+bootstrap
React:React全家桶(函数式组件hooks)+TS+Redux+Antd
项目创建
React :create-react-app react-version --template typescript
Vue3 :
npm install -g @vue/cli npm install -g @vue/cli-init
vue create vue3-version
初步文件结构
/assets
image.png
logo.png
/components
ColumnList.vue
DropDown.vue
...
/hooks
useURLloader.ts
...
/views
Home.vue
...
App.vue
main.ts
store.ts
router.ts
...
/assets
image.png
logo.png
/components
ColumnList
index.tsx
index.scss
DropDown
index.tsx
index.scss
...
/styles
index.scss
...
/hooks
useURLloader.tsx
...
/containers
...
/pages
...
/api
...
App.tsx
index.tsx
...
设计图拆分和组件属性分析
①顶部是一个header组件,存在两种状态,未登录和已登录,未登录时,有登录和注册两个按钮,登录后,出现一个可收缩下拉框,选项为其他功能
②接着是一个intro,纯文本简介区域,没什么交互
③ColumnList组件,每一个子组件是一个专栏
④LoadMore组件,按钮
组件设计与实现
ColumnList
需求分析
属性分析:
①list属性,数组,数组元素为包含子组件属性的object对象
②id:标识
③avatar:配图来源于网络,图片链接地址
④title:标题文本
⑤description:简介
设计实现
Vue
<template>
<div class="row">
<div v-for = "column in columnList" :key = "column.id" class="col-3 mb-4">
<div class = "card h-100 shadow-sm">
<div class="card-body text-center">
<img :src="column.avatar" :alt="column.title" class="rounded-circle border border-light w-25 my-3">
<h5 class="card-title">{{column.title}}</h5>
<p class="card-text text-left">{{column.description}}</p>
<a href="#" class="btn btn-outline-primary">进入专栏</a>
</div>
</div>
</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, PropType, computed } from 'vue'
export interface ColumnProps {
id : number
title : string
avatar ?: string
description : string
}
export default defineComponent({
name: 'ColumnList',
props: {
list: {
type: Array as PropType<ColumnProps[]>,
required: true
}
},
setup (props) {
const columnList = computed(() => {
return props.list.map(column => {
if (!column.avatar) column.avatar = require('@/assets/logo.png')
return column
})
})
return {
columnList
}
}
})
</script>
React
import { FC } from 'react'
import { Row, Col } from 'antd'
import Column, { ColumnProps } from './Column'
interface ColumnListProps {
list : ColumnProps[]
}
const ColumnList : FC<ColumnListProps> = (props) => {
const {list} = props
return (
<div>
<Row
gutter={[32,32]}
>
{
list.map(item => {
return (
<Col span={6}>
<Column {...item}/>
</Col>
)
})
}
</Row>
</div>
)
}
export default ColumnList;
import { FC } from 'react'
import { Card, Button, Avatar } from 'antd'
const { Meta } = Card
export interface ColumnProps {
id: number
title: string
avatar?: string
description: string
}
const Column: FC<ColumnProps> = (props) => {
const {
id,
title,
avatar,
description,
} = props
return (
<Card
hoverable
id={id.toString()}
actions={[
<Button>进入专栏</Button>
]}
>
<Meta
title={title}
description={description}
avatar={<Avatar src={avatar} />}
/>
</Card>
)
}
export default Column;
GlobalHeader
需求分析
属性分析:
①user属性,对象,用户信息
②isLogin,布尔值,是否登录
③name,名字
④id,标识符
设计实现
Vue
<template>
<nav class="navbar navbar-dark bg-primary justify-content-between mb-4 px-4">
<router-link class="navbar-brand" to="/">小胖专栏</router-link>
<ul v-if="!user.isLogin" class="list-inline mb-0">
<li class="list-inline-item">
<a href="#" class="btn btn-outline-light my-2">登陆</a>
</li>
<li class="list-inline-item">
<a href="#" class="btn btn-outline-light my-2">注册</a>
</li>
</ul>
<ul v-else class="list-inline mb-0">
<li class="list-inline-item">
<a href="#" class="btn btn-outline-light my-2">你好, {{user.name}}</a>
</li>
</ul>
</nav>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export interface UserProps {
isLogin : boolean
name ?: string
id ?: number
}
export default defineComponent({
name: 'GlobalHeader',
props: {
user: {
type: Object as PropType<UserProps>,
required: true
}
}
})
</script>
React
import { FC } from 'react'
import { Layout, Button } from 'antd'
interface userProps {
isLogin: boolean
name?: string
id?: string
}
const GlobalHeader: FC<userProps> = (props) => {
const {
isLogin,
name,
id
} = props
return (
<Layout.Header>
<h3 style={{
color: 'white',
float: 'left'
}}>小胖专栏</h3>
<div
style={{
color: 'white',
float: 'right'
}}
>
{
isLogin ?
<Button>你好 { name }</Button>
:
<>
<Button>登录</Button> | <Button>注册</Button>
</>
}
</div>
</Layout.Header>
)
}
export default GlobalHeader;
DropDown
需求分析
登陆后点击按钮,出现下拉菜单,内含登陆后的可操作功能,点击下拉菜单以外区域,下拉菜单自动收回
设计实现
Vue
<template>
<div class="dropdown" ref="dropdownRef">
<a href="#" class="btn btn-outline-light my-2 dropdown-toggle" @click.prevent="toggleOpen">{{
title
}}</a>
<ul class="dropdown-menu" :style="{display:'block'}" v-if="isOpen" >
<slot></slot>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue'
import useClickOutside from '../hooks/useClickOutside'
export default defineComponent({
name: 'DropDown',
props: {
title: {
type: String,
required: true
}
},
setup () {
const isOpen = ref(false)
const dropdownRef = ref<null | HTMLElement>(null)
const toggleOpen = () => {
isOpen.value = !isOpen.value
}
const isClickOutside = useClickOutside(dropdownRef)
watch(isClickOutside, () => {
isClickOutside.value && isOpen.value === true && toggleOpen()
})
return {
isOpen,
toggleOpen,
dropdownRef
}
}
})
</script>
<template>
<li class="dropdown-option"
:class="{ 'is-disabled' : disabled }"
>
<slot></slot>
</li>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'DropDownItem',
props: {
disabled: {
type: Boolean,
default: false
}
}
})
</script>
<style>
.dropdown-option.is-disabled * {
color: #6c757d;
pointer-events: none;
background-color: transparent;
}
</style>
React(目前还未涉及交互,简单实现,后续修改)
import { FC } from 'react'
import { Dropdown, Button, Menu } from 'antd'
// , ReactNode
// import DropDownItem from './DropDownItem'
// interface DropDownProps {
// menu : ReactNode
// }
// type IDropDownComponents = FC & {
// item: FC
// }
const DropDown : FC = (props) => {
const {
children
} = props
const menu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.antgroup.com">
编辑文章
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.aliyun.com">
新建文章
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.aliyun.com">
退出登录
</a>
</Menu.Item>
</Menu>
)
return (
<Dropdown overlay={menu} placement="bottomLeft" arrow>
<Button>{children}</Button>
</Dropdown>
)
}
export default DropDown;