一个页面的内容如何展示,往往需接口请求之后才能完全确认。
因此,大多数页面在默认时,都会展示一个 Loading 提示,或者是骨架展示。等到接口请求完成之后,再切换成想要展示的内容。
这是我们日常开发中最常见的需求。那么在没有 React/Vue 等框架的帮助下,我们如何能够方便快捷的做到展示内容的切换呢?
首先,使用 create react app 创建一个项目。
本文的实例项目地址:点击这里
因为默认的 demo 中,有了自己的 DOM 渲染,因此我们需要清空他们。将 App.tsx 修改为如下即可
1import React from 'react';2function App() {3return (4<div className="App"></div>5);6}78export default App;
我们需要一个类似于 axios.get
或者 $.get
这样的方法,去请求接口。代码封装如下,该方法单独成为一个模块,
实践中,我们还要根据需求,扩展 post、put、delete 方法
10// src/request.ts20// 简易版,未考虑参数情况,请勿运用于实践30export function get<T>(url: string): Promise<T> {40return new Promise((resolve, reject) => {50const XHR = new XMLHttpRequest()60XHR.open('GET', url, true)70XHR.send()8090XHR.onreadystatechange = () => {10if (XHR.readyState == 4) {11if (XHR.status == 200) {12try {13resolve(JSON.parse(XHR.responseText))14} catch(e) {15reject(e)16}17} else {18reject(new Error(XHR.statusText))19}20}21}22})23}
实践项目中,有许多要从后端请求数据的接口。我们统一封装在一个模块里
10// src/api.ts20import {get} from './request'30import news, {Newspaper} from './data'40506070export function newsApi() {80// 本来应该如此请求接口,但是由于跨域限制,因此我们这里模拟该接口的行为返回数据 mock90// return get<any>('https://news-at.zhihu.com/api/4/news/latest')10return new Promise<Newspaper>((resolve) => {11setTimeout(() => {12resolve(news)13}, 1000)14})15}1617export function other1Api(url: string) {}1819export function other2Api(url: string) {}2021export function other3Api(url: string) {}2223// 实践中可能还会有更多 api 请求
此处特殊的地方:由于跨域的限制,get 请求在开发中并不能请求成功。因此我们使用 mock 的方法模拟请求。
我们可以使用配置本地代理的方式来解决跨域问题。
因此这里会额外弄一个数据来源的模块
10// src/data.ts20export interface Story {30ga_prefix: string,40hint: string,50id: number,60image_hue: string,70title: string,80type: number,90url: string10}1112export interface TopStore extends Story {13image: string14}1516export interface ListStore extends Story {17images: string[]18}1920export interface Newspaper {21date: string,22stories: ListStore[]23top_stories: TopStore[]24}2526const news: Newspaper = {27"date": "20210112",28"stories": [29{ "image_hue": "0xb37b99", "title": "在儿童教育中该不该运用惩罚?怎么用?", "url": "https://daily.zhihu.com/story/9731933", "hint": "鸽子Gezi · 3 分钟阅读", "ga_prefix": "011207", "images": ["https://pic4.zhimg.com/v2-23d60782cb5b0b39698364438a24547b.jpg?source=8673f162"], "type": 0, "id": 9731933 },30{ "image_hue": "0x2f3964", "title": "中国不同城市的夜景都有哪些特色?", "url": "https://daily.zhihu.com/story/9731939", "hint": "星球研究所 · 8 分钟阅读", "ga_prefix": "011207", "images": ["https://pic1.zhimg.com/v2-c99ea0a523f595b142380603b7086a78.jpg?source=8673f162"], "type": 0, "id": 9731939 },31{ "image_hue": "0x0c729e", "title": "在游戏公司的音频部门工作是一种怎样的体验?", "url": "https://daily.zhihu.com/story/9731921", "hint": "腾讯天美工作室群 · 8 分钟阅读", "ga_prefix": "011207", "images": ["https://pic3.zhimg.com/v2-9013f69bfdf8c61b3232c43ee55971cb.jpg?source=8673f162"], "type": 0, "id": 9731921 },32{ "image_hue": "0x241e19", "title": "如果不得已要熬夜,怎么将熬夜的伤害减到最轻?", "url": "https://daily.zhihu.com/story/9731911", "hint": "浩浩耗 · 2 分钟阅读", "ga_prefix": "011207", "images": ["https://pic4.zhimg.com/v2-d327b0dcdabff3ad1121900dd26ec646.jpg?source=8673f162"], "type": 0, "id": 9731911 },33{ "image_hue": "0x342a18", "title": "哪双球鞋适合春节穿?", "url": "https://daily.zhihu.com/story/9731924", "hint": "Ricki · 6 分钟阅读", "ga_prefix": "011207", "images": ["https://pic2.zhimg.com/v2-388a92dae4d8c9fe3423ef2a02c25f06.jpg?source=8673f162"], "type": 0, "id": 9731924 },34{ "image_hue": "0x959199", "title": "瞎扯 · 如何正确地吐槽", "url": "https://daily.zhihu.com/story/9731914", "hint": "VOL.2569", "ga_prefix": "011206", "images": ["https://pic1.zhimg.com/v2-6e6a28ec3807dd74a29f4502c5bb25ce.jpg?source=8673f162"], "type": 0, "id": 9731914 }35],36"top_stories": [37{ "image_hue": "0x45252b", "hint": "作者 / 混乱博物馆", "url": "https://daily.zhihu.com/story/9731891", "image": "https://pic1.zhimg.com/v2-ca39463c945a2d8decc21575a42edcf0.jpg?source=8673f162", "title": "人类可以只吃肉不吃菜吗?", "ga_prefix": "011107", "type": 0, "id": 9731891 },38{ "image_hue": "0x402d39", "hint": "作者 / Justin Lee", "url": "https://daily.zhihu.com/story/9731821", "image": "https://pic2.zhimg.com/v2-41051830be11f2cecb10dad435d67f40.jpg?source=8673f162", "title": "法国人很浪漫,或许只是一种错误的刻板印象?", "ga_prefix": "010907", "type": 0, "id": 9731821 },39{ "image_hue": "0x2a301e", "hint": "作者 / 知乎用户", "url": "https://daily.zhihu.com/story/9731797", "image": "https://pic1.zhimg.com/v2-20daa7834321aee53f4a87ca2907b596.jpg?source=8673f162", "title": "接到了不同的 OFFER 应该怎么比较?", "ga_prefix": "010807", "type": 0, "id": 9731797 },40{ "image_hue": "0x999391", "hint": "作者 / Derrick Zhang", "url": "https://daily.zhihu.com/story/9731703", "image": "https://pic4.zhimg.com/v2-da79a55f96ace676c433a7c5adde99ab.jpg?source=8673f162", "title": "2021 年了,你还愿意买 Kindle 吗?", "ga_prefix": "010607", "type": 0, "id": 9731703 },41{ "image_hue": "0x172121", "hint": "作者 / 苏澄宇", "url": "https://daily.zhihu.com/story/9731647", "image": "https://pic2.zhimg.com/v2-ef49f243658edb316fd306be39e2f9be.jpg?source=8673f162", "title": "野生动物吃鱼不会被刺卡住吗?", "ga_prefix": "010407", "type": 0, "id": 9731647 }42]43}4445export default news;
针对某一个功能的处理。此处页面模块的作用,就是请求数据,并且渲染页面。
首先在 index.html 中,添加一个容器标签,我们会将内容渲染到该容器中。
<div id="news"></div>
然后处理页面逻辑
10// src/render.ts20import {newsApi} from './api'3040const news = document.querySelector('#news')5060if (!news) {70throw new Error('容器元素不存在')80}9010// 初始值11news.innerHTML = '数据加载中...'1213newsApi().then(res => {14const top = `15<div class="top-container">16${res.top_stories.map(item => (17`<a class="item" href="${item.url}">18<img src="${item.image}" alt="" />19<div>${item.title}</div>20</a>`21)).join('')}22</div>23`2425const list = `26<div class="list-container">27${res.stories.map(item => (28`<a class="item" href="${item.url}">29<img src="${item.images[0]}" alt="" />30<div>${item.title}</div>31</a>`32)).join('')}33</div>34`35news.innerHTML = `${top}${list}`36})
最后在入口模块 index.tsx
中,引入 render 模块即可。
1// src/index.tsx2import './render'
一个简单的模块化案例,就这样完成了。