精进TypeScript--【类型设计】倾向选择总是代表有效状态的类型

如果你看不到代码所操作的数据或数据类型,代码就很难理解。这就是类型系统的一大优势:通过写出类型,让你的代码的读者可以看到它们,而且这也将使得你的代码变得易懂。

要记住的事情:

  • 既代表有效状态又代表无效状态的类型,很可能使得代码混乱,容易出错
  • 优先选择只代表有效状态的类型。即使它们较长或较难表达,但最终会为你节省时间和较少痛苦

假设你正在构建一个Web应用程序,允许你选择一个页面,加载该页面的内容,然后显示它:

interface State {
  pageText: string;
  isLoading: boolean;
  error?: string;
}

当你编写代码来渲染页面时,你需要考虑所有这些领域:

function renderPage(state: State) {
  if (state.error) {
    return `Error! Unable to load ${currentPage}: ${state.error}`;
  } else if (state.isLoading) {
    return `Loading ${currentPage}...`;
  }
  return `<h1>${currentPage}</h1>\n${state.pageText}`;
}

上面的做法对吗?如果 isLoading 和 error 都设置了呢?是显示加载信息好还是错误信息好?这个很难说,目前信息量不够。

或者,如果你要写一个changePage函数呢?比如:

async function changePage(state: State, newPage: string) {
  state.isLoading = true;
  try {
    const response = await fetch(getUrlForPage(newPage));
    if (!response.ok) {
      throw new Error(`Error! Unable to load ${newPage}: ${response.statusText}`);
    }
    const text = await response.text();
    state.isLoading = false;
    state.pageText = text;
  } catch(e) {
    state.error = '' + e;
  }
}

这里有很多问题:

  • 在错误的情况下,我们忘记将 state.isLoading 设置为 false
  • 我们没有清除 state.error,所以如果之前的请求失败,那么你会一直看到这个错误信息,而不是加载信息
  • 如果用户在页面加载过程中再次刷新页面,谁也不知道会发生什么。他们可能会看到一个新的页面,然后出现一个错误:或者看到第一个页面,而不是第二个页面,这取决于响应回来的顺序

问题在于状态包括的信息太少:哪个请求失败了?哪个正在加载?或者包括的信息太多:State 类型允许设置 isLoading 和 error,即使这代表一个无效的状态。这使得 render() 和 changePage() 都无法很好地实现。

这里有一种更好的方式来表示应用状态:

interface RequestPending {
  state: 'pending';
}
interface RequestError {
  state: 'error';
  error: string;
}
interface RequestSuccess {
  state: 'ok';
  error: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess;

interface State {
  currentPage: string;
  requests: {[page: string]: RequestState};
}

这里使用一个标签联合类型来明确地模拟网络请求可能处于的不同状态。尽管这个版本的状态要更长,但它有一个巨大的优势,就是不接受无效 状态。当前页面是基于显式建模的,你发出的每个请求的状态也是基于显式建模的。因此,renderPage 和 changePage 函数很容易实现:

function renderPage(state: State) {
  const {currentPage} = state;
  const requestState = state.requests[currentPage];
  switch (requestState.state) {
    case 'pending':
      return `Loading ${currentPage}...`;
    case 'error':
      return `Error! Unable to load ${currentPage}: ${requestState.error}`;
    case 'ok':
      return `<h1>${currentPage}</h1>\n${requestState.pageText}`;
    case default:
      return '';
  }
}

async function changePage(state: State, newPage: string) {
  state.requests[newPage] = {state: 'pending'};
  state.currentPage = newPage;
  try {
    const response = await fetch(getUrlForPage(newPage));
    if (!response.ok) {
      throw new Error(`Error! Unable to load ${newPage}: ${response.statusText}`);
    }
    const pageText = await response.text();
    state.requests[newPage] = {state: 'ok', pageText};
  } catch(e) {
    state.requests[newPage] = {state: 'error', error: '' + e};
  }
}

和第一次实现相比,歧义完全消失了。当前页面是什么很清楚,每一个请求正好对应一个状态。如果用户在请求发出后改变了页面,那也没有问题。旧的请求仍然会完成,但不会影响 UI。

相关推荐

  1. TypeScript--优先选择类型声明而不类型断言

    2024-04-07 20:04:02       43 阅读
  2. TypeScript--习惯结构类型(Structual Typing)

    2024-04-07 20:04:02       40 阅读
  3. 信号状态类型

    2024-04-07 20:04:02       47 阅读
  4. TypeScript数组类型

    2024-04-07 20:04:02       29 阅读
  5. TypeScript交叉类型

    2024-04-07 20:04:02       35 阅读
  6. TypeScript中什么类型接口?

    2024-04-07 20:04:02       38 阅读
  7. C++之路(4)复合类型

    2024-04-07 20:04:02       52 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-07 20:04:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-07 20:04:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-07 20:04:02       82 阅读
  4. Python语言-面向对象

    2024-04-07 20:04:02       91 阅读

热门阅读

  1. Python—容器

    2024-04-07 20:04:02       37 阅读
  2. [xboard]real6410-3 S3C6410光盘资料与功能测试

    2024-04-07 20:04:02       32 阅读
  3. 力扣(数组)分发饼干

    2024-04-07 20:04:02       26 阅读
  4. vue-router v4.x命名路由,编程式跳转

    2024-04-07 20:04:02       30 阅读
  5. 独孤思维:1小时卖了80单

    2024-04-07 20:04:02       36 阅读
  6. shell脚本规范及变量类型、置换

    2024-04-07 20:04:02       38 阅读
  7. zynq gst-launch-1.0相关

    2024-04-07 20:04:02       31 阅读
  8. 顺序表应用——通讯录实现

    2024-04-07 20:04:02       32 阅读