前言

目前vue3还处于beta版本,离正式版的发布还有一段时间.在此之前我们可以对vue3新特性 composition api 做一个demo练习,加深对其具体应用场景的理解.

相关文档API

完整项目地址

 

Demo需求

                                                      

上图为Demo最终展现结果.Demo总共有三张页面:首页,商品详情页和登录页.

首页有"猜你喜欢"和"今日推荐"两个数据列表.点击列表标题会随机触发列表项的颤动.

点击列表项会获取到商品id进入到商品详情页面并展现出商品的详细信息.

点击头部的登录按钮会进入登录页面,用户登陆后会持久化保存登录状态.

 

首页开发

                                           

代码描述:

和vue2相比,vue3多了一个setup函数.它是整个组件执行的入口.以前我们在组件中编写data存储状态,编写computed计算属性,编写method函数,编写生命周期来做一些特定操作等等.如今这些行为我们全部可以放到setup函数中实现.

在setup函数中我们返回了一个对象,里面包含了三个数据likes,recommends和inquire函数.likes和recommends是通过调用useData函数(这是用户自定义的函数)返回的数据,inquire是通过调用useDetailPage函数返回的一个函数.最终通过setup导出才能被template模板渲染,而导出的函数能被模板上的事件触发.

我们现在追踪一下useData函数的具体实现.其中我们要着重关注两个新API:ref和onMounted. let likes = ref({});这句代码的含义是定义了一个响应式变量likes,它的初始值为空对象.onMounted顾名思义就是生命周期函数mounted,里面接受一个函数.当组件挂载完毕便会执行init函数请求服务器端的数据,最后将返回的结果赋值给likes和recommends,如此template模板上的"猜你喜欢"和"今日推荐"两个列表数据便渲染出来了.使用ref时需要注意它的赋值方式是调用.value来接受值.

在useDetailPage函数中,我们可以看到如何在vue3中使用路由.运行useRouter()获取router对象,再调用.push方法即可跳转,同理调用.back 方法可实现回退.

vue3它为什么要将数据状态,计算属性还有函数等等都放在setup函数中呢?这样代码不是显的很臃肿吗?

在setup函数中,我们可以定义许多功能耦合度很高的独立小单元函数,将数据状态和函数放在一起.比如下面的useData函数,里面盛放的数据就是likes和recommends,而如何获取likes和recommends的功能函数或者操作它们的相关方法都可以和数据定义放一起.这样就可以将组件中的逻辑进行更小密度的拆分,如果外界需要相关的数据和方法就可以导出供其调用.

首页源代码:

<template>
  <div class="home">
    <Header :has_back="false" />
    <Product :items="likes" @inquire="inquire" />
    <Product :items="recommends" @inquire="inquire" />
  </div>
</template>

<script>
// @ is an alias to /src
import Header from "@/components/Header.vue";
import { ref, onMounted } from "vue";
import Product from "../components/Product";
import { useRouter } from "vue-router";
import { post } from "../util/tool";

export default {
  components: {
    Header,
    Product
  },
  setup() {
    const { likes, recommends } = useData();
    const { inquire } = useDetailPage();
    return {
      likes,
      recommends,
      inquire
    };
  }
};

/**
 * 进入详情页面相关操作
 */
function useDetailPage() {
  const router = useRouter();
  function inquire(data) {
    const { id } = data;
    router.push(`/detail/${id}`);
  }
  return {
    inquire
  };
}

/**
 * 获取页面的数据
 */
function useData() {
  let likes = ref({});
  let recommends = ref({});

  onMounted(() => {
    init();
  });

  function init() {
    return new Promise(resolve => {
      post({
        url: "/api/home"
      }).then(result => {
        likes.value = result.likes;
        recommends.value = result.recommends;
      });
    });
  }

  return {
    likes,
    recommends
  };
}
</script>

 

列表组件开发

我们现在来看一下首页当中的Product组件的实现源码.(下面源码中省略了css,完整代码请打开最上面的项目地址)

1.在vue3中父子组件是如何通信的呢?

在setup函数中含有两个参数props和context.其中props用来获取父组件传递过来的参数,而context.emit用于调用父组件的方法.

       2.vue3中如何使用计算属性?

在computed函数中传递一个回调函数并返回某个值.

3.vue3中如何通过ref获取dom节点?

在下面的lightHanlder函数中首先定义一个响应式的数组doms,并导出到模板template中使用.我们可以看到模板中的这个用法:

:ref="ref=>doms[index]=ref",:ref右边接的是一个函数,函数的参数ref也就是相应的原生dom对象,将列表的原生dom对象都赋值到

doms数组中.lightHanlder函数主要是操作原生dom对象执行动画.

 

<template>
  <div class="Product">
    <p class="title" @click="lightOne">{{title}}</p>
    <div
      class="item"
      v-for="(item,index) in list"
      :key="item.id"
      @click="inquire(item)"
      :ref="ref=>doms[index]=ref"
    >
      <p class="item_top box">
        <i class="lt">{{item.name}}</i>
        <i class="gt">{{item.price}}</i>
      </p>
      <p class="item_bottom">{{item.desc}}</p>
    </div>
  </div>
</template>
<script>
import { computed, ref } from "vue";

export default {
  props: {
    items: Object
  },
  setup(props, context) {
    const title = computed(() => {
      return props.items ? props.items.title : "";
    });
    const list = computed(() => {
      return props.items ? props.items.data : [];
    });
    function inquire(item) {
      context.emit("inquire", item);
    }

    const { doms, lightOne } = lightHanlder();

    return {
      title,
      list,
      inquire,
      doms,
      lightOne
    };
  }
};

function lightHanlder() {
  const doms = ref([]);
  function lightOne() {
    const len = doms.value.length;
    if (len == 0) {
      return false;
    }
    const index = parseInt(Math.random() * len);
    doms.value[index].classList.add("animated");
    setTimeout(() => {
      if (doms.value[index]) {
        doms.value[index].classList.remove("animated");
      }
    }, 1000);
  }
  return {
    doms,
    lightOne
  };
}
</script>

 

详情页开发

                                                      

1.获取路由参数id?

通过调用useRoute()方法返回route对象,在其params属性中可以获取到相关的路由参数.

2.reactive如何使用?

reactive和ref的功能类似都是定义响应式的状态,只不过reactive里面通常装一些对象数据.比如界面上有大量的表单数据时,我们就可以考虑使用reactive来定义表单数据的状态.另外reactive赋值时不需要像ref调用.value来赋值.

 

源代码

<template>
  <div class="detail">
    <Header title="详情页" />
    <div class="item">
      <p class="item_top box">
        <i class="lt">id</i>
        <i class="gt">{{detailDetail.form.id}}</i>
      </p>
    </div>
    <div class="item">
      <p class="item_top box">
        <i class="lt">名称</i>
        <i class="gt">{{detailDetail.form.price}}</i>
      </p>
    </div>
    <div class="item">
      <p class="item_top box">
        <i class="lt">数量</i>
        <i class="gt">{{detailDetail.form.count}}</i>
      </p>
    </div>
    <div class="item">
      <p class="item_top box">
        <i class="lt">价格</i>
        <i class="gt">{{detailDetail.form.price}}</i>
      </p>
    </div>
    <div class="item high">
      <p class="item_top box">
        <i class="lt">照片</i>
        <i class="gt">
          <img :src="detailDetail.form.img" />
        </i>
      </p>
    </div>
    <div class="item high">
      <p class="item_top box">
        <i class="lt">描述</i>
        <i class="gt">{{detailDetail.form.desc}}</i>
      </p>
    </div>
  </div>
</template>
<script>
import Header from "../components/Header";
import { useRoute } from "vue-router";
import { reactive } from "vue";
import { post } from "../util/tool";

export default {
  components: {
    Header
  },
  setup() {
    const { detailDetail } = useData();
    return { detailDetail };
  }
};

function useData() {
  const route = useRoute();
  const { id } = route.params;
  const detailDetail = reactive({
    form: {
      count: "",
      date: "",
      desc: "",
      id: "",
      img: "",
      name: "",
      price: ""
    }
  });

  requestData();

  function requestData() {
    post({
      url: `/api/detail`,
      data: {
        id
      }
    }).then(res => {
      detailDetail.form = res;
    });
  }

  return { detailDetail };
}
</script>

 

登录页开发

  登录前:

                                         

 

登录后:

                                         

 

vue3中如何使用vuex?

vuex相关的配置可以保持与vue2中一样,但是在页面中获取vuex的数据和调用vuex中的mutations或者actions有点区别.

我们可以看下面的httpMethods函数,通过调用useStore()函数获取到store仓库,通过调用store.commit方法来调用mutations中的相关函数来改变vuex中的数据.怎么获取vuex中数据呢?看下一小结.

 

源代码

<template>
  <div class="login">
    <Header :has_login="false" title="登录" />
    <div class="form" v-if="!userInfo">
      <p class="line">
        <i class="label">用户名:</i>
        <input
          type="text"
          placeholder="请输入用户名"
          :value="user.user_name"
          @input="setForm({user_name:$event.target.value})"
        />
      </p>
      <p class="line">
        <i class="label">密码:</i>
        <input
          type="password"
          placeholder="请输入密码"
          :value="user.password"
          @input="setForm({password:$event.target.value})"
        />
      </p>
      <p class="btn">
        <a class="submmit" @click="login">登录</a>
      </p>
    </div>
    <div v-else>
      <div class="tag">{{userInfo.user_name}}殿下,欢迎您登录!</div>
      <p class="btn">
        <a class="submmit" @click="logout">退出登录</a>
      </p>
    </div>
  </div>
</template>
<script>
import Header from "../components/Header";
import { useStore } from "vuex";
import { reactive } from "vue";
import { post, storeUser, clearUser } from "../util/tool";
import UserState from "../components/UserState";

export default {
  components: {
    Header
  },
  setup() {
    const { user, setForm } = paramsHandler(); //表单参数
    const { login, logout } = httpMethods(user); //登录操作的函数
    const { userInfo } = UserState.setup(); //获取用户信息

    return {
      user,
      login,
      setForm,
      userInfo,
      logout
    };
  }
};

/**
 * 登录操作
 */
function httpMethods(user) {
  const store = useStore();

  function login() {
    const { user_name, password } = user;
    if (user_name.trim() === "" || password.trim() === "") {
      alert("账号密码不能为空");
      return false;
    }
    post({
      url: "/api/login",
      data: {
        user_name,
        password
      }
    }).then(res => {
      const user = {
        user_name,
        password: "",
        user_id: res.user_id
      };
      store.commit("setUser", user);
      storeUser(user);
    });
  }

  function logout() {
    store.commit("clearUser");
    clearUser(); //删除localstorage存储的用户信息
  }

  return {
    login,
    logout
  };
}

/**
 * 表单的参数处理
 */
function paramsHandler() {
  const user = reactive({
    user_name: "",
    password: ""
  });
  function setForm(data) {
    const key = Object.keys(data)[0];
    user[key] = data[key];
  }
  return {
    user,
    setForm
  };
}
</script>

 

计算逻辑的复用

细心的同学会发现在登录页面中我们是通过调用UserState.setup()来获取当前用户登录的状态,在这里需要引起格外注意的是UserState是一个组件.

我们在A组件中引用B组件并调用B组件中setup方法来获取数据,这和我们之前vue2中组件的使用方法就不太一样了.在vue2中我们定义的组件包含template,script,style这三部分,而在vue3里面我们可以定义一个组件而这个组件只包含script部分,这样就意味我们可以脱离template模板只复用组件的计算逻辑.

这样的计算逻辑复用有什么好处呢?

比如我的很多页面都需要获取登录状态,按照之前的做法就是每个组件都与vuex进行连接获取登录状态.而现在呢我们可以在vuex中获取登录状态的逻辑封装到UserState组件中,其他页面想要获取登录状态只需要调用UserState.setup()即可得到登录信息,而不用再一个个去连接vuex了.

  观察下面代码,在UserState中定义用户的登录状态信息userInfo,让了能让其监听到vuex的状态变化从而触发其他引用该状态的页面产生相应的变化,我们可以用watch也可以用computed让userInfo实时变化,最后将其导出.

UserState源码:

<script>
import { ref, watch } from "vue";
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const userInfo = ref(store.state.user);
    watch(
      () => {
        return store.state.user;
      },
      (newv, prev) => {
        userInfo.value = newv;
      }
    );
    return {
      userInfo
    };
  }
};
</script>

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐