[TOC]

01-文章详情结构

目标:能够根据模板搭建文章详情页面结构
步骤

  1. 将文章详情页面的模板拷贝到 pages 目录中
  2. 在 App 组件中配置文章详情页面的路由

    02-获取文章详情

    目标:能够获取文章详情数据
    步骤

  3. 创建文章详情 article 接口返回数据类型

  4. 创建获取文章详情数据的 action 类型,并合并到 RootAction 中
  5. 创建文章详情的 reducer 并合并到根 reducer 中
  6. 创建文章详情的 action 并发送请求获取数据
  7. 在文章详情页面中分发 action 获取数据

核心代码
data.d.ts 中:
// — 文章详情 —
export type ArticleDetail = {
art_id: string;
title: string;
pubdate: string;
aut_id: string;
aut_name: string;
aut_photo: string;
is_followed: boolean;
attitude: number;
content: string;
is_collected: boolean;
// 接口中缺失
comm_count: number;
like_count: number;
read_count: number;
};
export type ArticleDetailResponse = ApiResponse;
store.d.ts 中:
import type { ArticleDetail } from ‘./data’;

export type RootAction =
// …
ArticleAction;

export type ArticleAction = {
type: ‘article/get’;
payload: ArticleDetail;
};
reduers/article.ts 中:
import { ArticleDetail } from ‘@/types/data’;
import { ArticleAction } from ‘@/types/store’;

type ArticleState = {
// 文章详情
detail: ArticleDetail;
};

const initialState = {
detail: {},
} as ArticleState;

const article = (state = initialState, action: ArticleAction): ArticleState => {
if (action.type === ‘article/get’) {
return {
…state,
detail: action.payload,
};
}
return state;
};

export default article;
reduers/index.ts 中:
import article from ‘./article’;

const rootReducer = combineReducers({
// …
article,
});
actions/article.ts 中:
import { ArticleDetailResponse } from ‘@/types/data’;
import { RootThunkAction } from ‘@/types/store’;
import { http } from ‘@/utils’;

// 获取文章详情
export const getArticleById = (id: string): RootThunkAction => {
return async (dispatch) => {
const res = await http.get(/articles/${id});

dispatch({
type: ‘article/get’,
payload: res.data.data,
});
};
};
pages/Article/index.tsx 中:
import { useInitialState } from ‘@/utils/use-initial-state’;
import { getArticleById } from ‘@/store/actions/article’;

const Article = () => {
// 获取文章详情
const { detail } = useInitialState(
() => getArticleById(params.artId),
‘article’,
);
// …
};

03-渲染文章详情

目标:能够渲染文章详情信息
分析说明
参考:dayjs 本地换化格式
使用 dayjs 的本地化格式,可以展示:2021年10年24日 格式的日期
import dayjs from ‘dayjs’;
// 导入本地化格式插件
import localizedFormat from ‘dayjs/plugin/localizedFormat’;
dayjs.extend(localizedFormat);

// ‘2021-10-24 10:24:00’ => ‘2021年10月24日’
dayjs(detail.pubdate).locale(‘zh-cn’).format(‘LL’);
步骤

  1. 在获取文章详情的 action 中,格式化日期
  2. 在文章详情页面中,拿到文章详情数据,并渲染在页面中

核心代码
actions/article.ts 中:
// 导入 dayjs 相关包
import dayjs from ‘dayjs’;
import localizedFormat from ‘dayjs/plugin/localizedFormat’;
dayjs.extend(localizedFormat);

export const getArticleById = (id: string): RootThunkAction => {
return async (dispatch) => {
const res = await http.get(/articles/${id});

const detail = res.data.data;
dispatch({
type: ‘article/get’,
payload: {
…detail,
// 格式化日期:
pubdate: dayjs(detail.pubdate).locale(‘zh-cn’).format(‘LL’),
},
});
};
};
pages/Article/index.tsx 中:
const { detail } = useInitialState(
() => getArticleById(params.artId),
‘article’,
);

// 解构出文章详情数据:
const {
// art_id,
// aut_id,
// // 是否点赞
// attitude,
// // 是否收藏
// is_collected,
content,
is_followed,
aut_name,
aut_photo,
comm_count,
like_count,
pubdate,
read_count,
title,
} = detail;

// 在 JSX 中渲染数据…

04-渲染文章内容

目标:能够渲染 HTML 字符串格式的文章内容
分析说明
参考:React 的 dangerouslySetInnerHTML
参考:MDN innerHTML 安全问题
dangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案,用来在 JSX 中渲染 HTML 格式的字符串内容
通常来讲,使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击
HTML 5 中规定:不执行由 innerHTML 插入的