Vue3/React-对比实现仿知乎专栏--01

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;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟小胖砸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值