十二、答案

第 1 章:TypeScript 基础

  1. 五种基本类型是什么?

  2. string:表示一个 Unicode 字符序列

  3. number:表示整数和浮点数
  4. boolean:表示逻辑正确或错误
  5. undefined:表示一个尚未初始化的值
  6. null:表示无值

  7. 以下代码中的flag变量的推断类型是什么?

 const flag = false;

flag将被推断为boolean类型。

  1. 接口和类型别名之间有什么区别

主要区别在于类型别名不能从扩展或实现,就像使用接口一样。

  1. 以下代码有什么问题?
class Product {
  constructor(public name: string, public unitPrice: number) {}
}

let table = new Product();
table.name = "Table";
table.unitPrice = 700;

构造函数要求通过nameunitPrice。这里有两种解决问题的方法。

在构造函数中传递值:

let table = new Product("Table", 700);

将参数设置为可选:

class Product {
  constructor(public name?: string, public unitPrice?: number) {}
}
  1. 如果我们希望 TypeScript 程序支持 IE11,--target编译器选项应该是什么?

这应该是es5,因为 IE11 最多只支持 ES5 功能。

  1. 是否可以让 TypeScript 编译器传输 ES6 JavaScript 文件?如果是,怎么做?

对我们可以使用--allowJS设置让编译器传输 JavaScript 文件

  1. 我们如何防止console.log()语句进入我们的代码****

****我们可以使用 tslint 和"no-console"规则来强制执行这一点。这将是tslint.json中的规则:

{
 "rules": {
 "no-console": true
 }
}

第 2 章:TypeScript 3 的新增功能

  1. 我们有以下绘制点的函数:
function drawPoint(x: number, y: number, z: number) {
  ...
}

我们还有以下point变量:

const point: [number, number, number] = [100, 200, 300];

我们如何以简洁的方式调用drawPoint函数?

drawPoint(...point);
  1. 我们需要创建另一个版本的drawPoint函数,该函数可以通过传递 x、y 和 z 点值作为参数来调用:
drawPoint(1, 2, 3);

drawPoint的内部实现中,我们从元组数据类型[number, number, number]中提取点。我们如何使用所需的元组定义方法参数?

function drawPoint(...point: [number, number, number]) {
  ...
}
  1. 在您的drawPoint实现中,您如何使z点成为可选的?
function drawPoint(...point: [number, number, number?]) {
  ...
}
  1. 我们有一个名为getData的函数,它调用 web API 来获取一些数据。不同的 API 资源的数量还在增长,所以我们选择使用any作为返回类型:
function getData(resource: string): any {
  const data = ... // call the web API
  if (resource === "person") {
    data.fullName = `${data.firstName} ${data.surname}`;
  }
  return data;
}

我们如何利用unknown类型使getData类型更安全?

class Person {
 firstName: string;
 surname: string;
 fullName: string;
}

function getData(resource: string): unknown {
  const data = {};
  if (data instanceof Person) {
    data.fullName = `${data.firstName} ${data.surname}`;
  }
  return data;
}
  1. 我们可以使用什么build标志来确定哪些项目已经过时,需要在不进行重建的情况下重建?
tsc --build ... --dry --verbose 

第 3 章:React 和 TypeScript 入门

  1. 在开发过程中,允许调试器语句和登录控制台的TSLint设置是什么?
"rules": {
  "no-debugger": false,
  "no-console": false,
},
  1. 在 JSX 中,我们如何在类组件中显示带有名为buttonLabel的道具标签的按钮?
<button>{this.props.buttonLabel}</button>
  1. 我们如何使buttonLabel道具成为可选的,并默认这样做?

道具界面类型标注前使用?

interface IProps {
  buttonLabel?: string
}

在类组件的顶部实现一个静态defaultProps对象:

public static defaultProps = {
  buttonLabel: "Do it"
};
  1. 在 JSX 中,如果称为doItVisible的状态为 true,我们如何才能显示前面的按钮?假设我们已经声明了一个包含doItVisible的状态类型,并且它已经在构造函数中初始化
{this.state.doItVisible && <button>{this.props.buttonLabel}</button>}
  1. 我们如何为此按钮创建单击处理程序?
<button onClick={this.handleDoItClick}>{this.props.buttonLabel}</button>
private handleDoItClick = () => {
  // TODO: some stuff!
};
  1. 我们声明了一个包含doItDisabled的状态类型。它也已在构造函数中初始化。我们将如何设置此状态以在单击后禁用“执行”按钮?
private handleDoItClick = () => {
  this.setState({doItDisabled: true})
};
<button disabled={this.state.doItDisabled}>{this.props.buttonLabel}</button>
  1. 如果按钮在禁用时被单击,是否仍会调用单击处理程序?

  1. 在类组件中使用哪个生命周期方法将事件处理程序添加到存在于 React 组件中的 none React web 组件?

componentDidMount

  1. 然后,我们将使用哪个生命周期方法删除此事件处理程序?

componentWillUnmount

  1. 我们有一个名为Counter的功能组件。它需要包含一个名为count的状态和一个名为setCount的更新函数。如何定义此状态并将初始计数默认为 10?
const count, setCount = React.useState(10);
  1. 在前面的Counter组件中,我们有一个decrement函数,需要将count减少 1:
const decrement = () => {
  // TODO - reduce count by 1
};

如何实现这一点?

const decrement = () => {
  setCount(count - 1);
};

第 4 章:使用 React 路由的路由

  1. 我们有以下Route显示客户名单:
<Route path="/customers" component={CustomersPage} />

当页面为"/customers"时,CustomersPage组件会呈现吗? 对

  1. 当页面为"/customers/24322"时,CustomersPage组件会呈现吗? 对
  2. 我们只希望在路径为"/customers"时渲染CustomersPage组件。我们如何更改Route上的属性来实现这一点?

我们可以使用exact属性:

<Route exact={true} path="/customers" component={CustomersPage} />
  1. 可以处理路径"/customers/24322"Route是什么?它应该将"24322"放入一个名为customerId的路由参数中:
<Route exact={true} path="/customers/:customerId" component={CustomerPage} />
  1. 然后我们可以使用RouteComponentProps作为CustomerPage道具类型,通过props.match.params.``customerId访问customerId

我们如何捕捉不存在的路径以便通知用户?

确保所有的Route组件都包裹在Switch组件中。然后,我们可以向组件添加一个Route,该组件向用户呈现一条未找到的消息,作为开关中的最后一个Route

<Switch>
  <Route path="/customers/:customerId" component={CustomerPage} />
  <Route exact={true} path="/customers" component={CustomersPage} />
  <Route component={NotFoundPage} />
</Switch>
  1. 我们如何在CustomersPage中实现search查询参数?因此,"/customers/?search=Cool Company"会向客户展示酷公司的名称。

首先,我们需要在我们的课堂上使用RouteComponentProps类型的道具:

import { RouteComponentProps } from "react-router-dom";

class CustomersPage extends React.Component<RouteComponentProps, IState> { ... }

我们可以使用URLSearchParams获取search查询参数,并在componentDidMount生命周期方法中进行搜索:

public componentDidMount() {
  const searchParams = new URLSearchParams(props.location.search);
  const search = searchParams.get("search") || "";

  const products = await ... // make web service call to do search

  this.setState({ products });
}
  1. 过了一会儿,我们决定将"customer"路径更改为"clients"。我们如何实现这一点,以便用户仍然可以使用现有的"customer"路径,但让路径自动重定向到新的"client"路径。

我们可以使用Redirect组件将旧路径重定向到新路径:

<Switch>
 <Route path="/clients/:customerId" component={CustomerPage} />
 <Route exact={true} path="/clients" component={CustomersPage} />

 <Redirect from="/customers/:customerId" to="/clients/:customerId" />
 <Redirect exact={true} from="/customers" to="/clients" />

 <Route component={NotFoundPage} />
</Switch>

第五章:高级类型

  1. 我们有一个表示课程结果的界面,如下所示:
interface ICourseMark {
  courseName: string;
  grade: string;
}

我们可以按如下方式使用此界面:

const geography: ICourseMark = {
  courseName: "Geography",
  grade: "B"
} 

等级只能是 A、B、C 或 D。我们如何在此界面中创建更强类型的grade属性?

我们可以使用联合类型:

interface ICourseMark {
  courseName: string;
  grade: "A" | "B" | "C" | "D";
}
  1. 我们有以下函数,用于验证数字和字符串是否填充了值:
function isNumberPopulated(field: number): boolean {
  return field !== null && field !== undefined;
}

function isStringPopulated(field: string): boolean {
  return field !== null && field !== undefined && field !== "";
}

我们如何将这些组合成一个名为isPopulated的函数,并使用签名重载?

我们可以使用重载签名,然后在主函数中为field使用联合类型。然后我们可以在函数中使用typeof类型的 guard 来处理不同的逻辑分支:

function isPopulated(field: number): boolean 
function isPopulated(field: string): boolean
function isPopulated(field: number | string): boolean {
 if (typeof field === "number") {
 return field !== null && field !== undefined;
 } else {
 return field !== null && field !== undefined && field !== "";
 }
}
  1. 我们如何用泛型实现更灵活的isPopulated函数版本?

对于字符串的特殊代码分支,我们可以使用带有typeof类型保护的泛型函数:

function isPopulated<T>(field: T): boolean {
  if (typeof field === "string") {
    return field !== null && field !== undefined && field !== "";
  } else {
    return field !== null && field !== undefined;
  }
} 
  1. 我们有以下类型的阶段别名:
type Stages = {
  pending: 'Pending',
  started: 'Started',
  completed: 'Completed',
};

我们如何通过编程将其转换为 union 类型'Pending' | 'Started' | 'Completed'

我们可以使用keyof关键字:

type StageUnion = keyof Stages
  1. 我们有以下联合类型:
type Grade = 'gold' | 'silver' | 'bronze';

我们如何以编程方式创建以下类型?

type GradeMap = {
  gold: string;
  silver: string;
  bronze: string
};

我们可以按如下方式映射类型:

type GradeMap = { [P in Grade]: string }

第 6 章:组件模式

  1. React 为我们访问子组件提供了什么特殊属性?

一个名为children的属性

  1. 有多少组件可以与 React 上下文共享状态?

组件层次结构中的提供者组件下有我们喜欢的任意多个组件

  1. 在使用 React 上下文时,它使用什么模式允许我们使用上下文呈现内容?

渲染道具模式

  1. 一个组件中可以有多少渲染道具?

我们喜欢多少就有多少

  1. 一个组件中有多少儿童道具?

1.

  1. 我们只在产品页面上使用了withLoader。我们在ProductData.ts中有以下功能获取所有产品:
export const getProducts = async (): Promise<IProduct[]> => {
  await wait(1000);
  return products;
};

您可以通过使用withLoaderHOC 在产品页面上实现加载器微调器吗。

首先,我们将ProductPage拆分为一个容器和表示组件。呈现组件将呈现产品列表,并将其包装在withLoaderHOC 中:

import * as React from "react";
import { Link } from "react-router-dom";
import { IProduct } from "./ProductsData";
import withLoader from "./withLoader";

interface IProps {
  products: IProduct[];
  search: string;
}
const ProductList: React.SFC<IProps> = props => {
  const { products, search } = props;
  return (
    <ul className="product-list">
      {products.map(product => {
        if (
          !search ||
          (search &&
            product.name.toLowerCase().indexOf(search.toLowerCase()) > -1)
        ) {
          return (
            <li key={product.id} className="product-list-item">
              <Link to={`/products/${product.id}`}>{product.name}</Link>
            </li>
          );
        } else {
          return null;
        }
      })}
    </ul>
  );
};

export default withLoader(ProductList);

然后我们可以在ProductPage中使用它,其render方法如下:

public render() {
 return (
 <div className="page-container">
 <p>
 Welcome to React Shop where you can get all your tools for ReactJS!
 </p>
 <ProductList
 loading={this.state.loading}
 products={this.state.products}
 search={this.state.search}
 />
 </div>
 );
}
  1. 可以使用儿童道具模式创建加载器旋转器吗?因此,消费 JSX 应该是这样的:
<Loader loading={this.state.loading}>
  <div>
    The content for my component ...
  </div>
</Loader>

如果是的话,试一试吧?

import * as React from "react";

interface IProps {
  loading: boolean;
}

const Loader: React.SFC<IProps> = props =>
  props.loading ? (
    <div className="loader-overlay">
      <div className="loader-circle-wrap">
        <div className="loader-circle" />
      </div>
    </div>
  ) : props.children ? (
    <React.Fragment>{props.children}</React.Fragment>
  ) : null;

export default Loader;

第 7 章:使用表单

  1. 扩展我们的通用Field组件,以包括使用本机数字输入的数字编辑器。

  2. 首先,在type道具中增加"Number"

interface IFieldProps {
  ...
  type?: "Text" | "Email" | "Select" | "TextArea" | "Number";
}
  • 在呈现input时包括"Number"类型:
{(type === "Text" || type === "Email" || type === "Number") && (
  <input
    type={type.toLowerCase()}
    id={name}
    value={context.values[name]}
    onChange={e => handleChange(e, context)}
    onBlur={e => handleBlur(e, context)}
  />
)}
  1. 在“联系我们”表单上设置一个紧急字段,以指示响应的紧急程度。该字段应为数字。

在注释字段之后立即添加以下字段:

<Form.Field name="urgency" label="How urgent is a response?" type="Number" />
  1. 在泛型Form组件中实现一个新的验证器函数,用于验证一个数字是否介于其他两个数字之间。

Form.tsx中增加以下功能:

export const between: Validator = (
  fieldName: string,
  values: IValues,
  bounds: { lower: number; upper: number }
): string =>
  values[fieldName] &&
  (values[fieldName] < bounds.lower || values[fieldName] > bounds.upper)
    ? `This must be between ${bounds.lower} and ${bounds.upper}`
    : "";
  1. 在紧急字段上实施验证规则,以确保其介于 1 和 10 之间。

  2. 首先将between验证器导入ContactUs.tsx

import { between, Form, ISubmitResult, IValues, minLength, required } from "./Form";
  • ContactUs.tsxvalidationRules道具中增加紧急规则:
validationRules={{
  email: { validator: required },
  name: [{ validator: required }, { validator: minLength, arg: 3 }],
  urgency: [{ validator: between, arg: { lower: 1, upper: 10 } }]
}}
  1. 当用户在没有键入任何内容的情况下点击一个字段时,就会触发我们的验证。当一个字段失去焦点但仅当它被更改时,我们如何触发验证?试着实现一下。

  2. 我们需要跟踪一个字段是否在表单状态下被触摸:

interface ITouched {
 [key: string]: boolean;
}

interface IState {
  touched: ITouched;
  ...
}
  • 我们在构造函数中将每个字段的touched值初始化为false
constructor(props: IFormProps) {
  super(props);
  const errors = {};
  const touched = {};
  Object.keys(props.defaultValues).forEach(fieldName => {
    errors[fieldName] = [];
    touched[fieldName] = false;
  });
  this.state = {
    errors,
    submitted: false,
    submitting: false,
    touched,
    values: props.defaultValues
  };
}
  • setValue方法中,我们将被更新字段的touched值更新为true
private setValue = (fieldName: string, value: any) => {
  const newValues = { ...this.state.values, [fieldName]: 
   value };
  const newTouched = { ...this.state.touched, [fieldName]:  
   true };
  this.setState({ values: newValues, touched: newTouched });
};
  • 在 validate 方法的顶部,我们检查字段是否已被触摸,如果未被触摸,我们将返回一个空数组以指示字段有效:
private validate = (fieldName: string, value: any): string[] => {
  if (!this.state.touched[fieldName]) {
    return [];
  }
  ...
};

第 8 章:反应重复

  1. 是否需要typeaction 对象中的属性?这个属性需要被称为type吗?我们可以叫它别的吗?

type属性在 action 对象中是必需的,必须调用type

  1. action 对象可以包含多少属性?

我们喜欢多少就有多少!对于type属性,它需要至少包含一个。然后,它可以包括我们需要的许多其他属性,以便减速器更改状态,但这通常集中在一个附加属性中。因此,通常一个动作会有一个或两个属性。

  1. 什么是动作创造者?

动作创建者是返回动作对象的函数。组件调用这些函数以更改存储中的状态。

  1. 为什么我们在 React shop 应用程序的 Redux 商店中需要 Redux Thunk?

默认情况下,Redux 存储无法管理异步操作创建者。需要将中间件添加到 Redux 存储中,以方便异步操作创建者。Redux Thunk 是我们为此添加的中间件。

  1. 我们能不能用些别的东西,而不是 Redux Thunk?

对我们本可以创建自己的中间件。我们也可以使用其他成熟的库,比如 Redux Saga。

  1. 在我们刚刚实现的basketReducer中,为什么不使用push函数将项目添加到篮子状态?突出显示的行有什么问题?
export const basketReducer: Reducer<IBasketState, BasketActions> = (
  state = initialBasketState,
  action
) => {
  switch (action.type) {
    case BasketActionTypes.ADD: {
      state.products.push(action.product);
    }
  }
  return state || initialBasketState;
};

这直接改变了产品的状态,使功能不纯。这是因为我们改变了 state 参数,它超出了我们函数的范围。在本例中,如果违反此规则,则在单击“添加到篮子”按钮时,呈现页面上的篮子摘要不会增加。

第 9 章:与 RESTful API 的交互

  1. 如果在浏览器中运行以下代码,控制台中的输出将是什么?
try {
 setInterval(() => {
  throw new Error("Oops");
 }, 1000);
} catch (ex) {
  console.log("Sorry, there is a problem", ex); 
}

我们会收到一条消息说发生了未捕获错误(Oops)。The console.log语句无法到达。

  1. 假设 9999 后不存在,如果我们在浏览器中运行以下代码,控制台中的输出会是什么:
fetch("https://jsonplaceholder.typicode.com/posts/9999")
  .then(response => {
    console.log("HTTP status code", response.status);
    return response.json();
  })
  .then(data => console.log("Response body", data))
  .catch (error => console.log("Error", error));

关键的一点是,HTTP 错误不会在带有fetch函数的catch方法中得到处理。

  1. 如果我们对axios做了类似的练习,那么在运行以下代码时控制台中的输出是什么?
axios
  .get("https://jsonplaceholder.typicode.com/posts/9999")
  .then(response => {
    console.log("HTTP status code", response.status);
  })
  .catch(error => {
    console.log("Error", error.response.status);
  });

关键的一点是,HTTP 错误确实会在带有axioscatch方法中得到处理。

  1. axios相比,使用本机fetch有什么好处?

如果我们的目标是现代浏览器(而不是 IE),并且只需要简单的 REST API 交互,那么fetch可以说比axios更有利,因为我们的代码不依赖于第三方代码。由于执行的非本机代码较少,它也可能会运行得更快一些。

  1. 我们如何向以下axios请求添加承载令牌?
axios.get("https://jsonplaceholder.typicode.com/posts/1")
  1. 第二个参数是一个对象文字,它有一个可以包含请求 HTTP 头的header属性:
axios
  .get("https://jsonplaceholder.typicode.com/posts/1", {
    headers: {
      "Authorization": `Bearer ${token}`
    }
  });
  1. 我们正在使用以下axios``PUT请求更新帖子标题:
axios.put("https://jsonplaceholder.typicode.com/posts/1", {
  title: "corrected title", 
  body: "some stuff"
});

身体没有改变,只是我们想要更新的标题。我们如何才能将其更改为PATCH请求,以使这个 REST 呼叫更有效?

axios.patch("https://jsonplaceholder.typicode.com/posts/1", {
  title: "corrected title"
});
  1. 我们已经实现了一个功能组件来显示帖子。它使用以下代码从 REST API 获取 post:
React.useEffect(() => {
  axios
    .get(`https://jsonplaceholder.typicode.com/posts/${id}`)
    .then(...)
    .catch(...);
});

前面的代码有什么问题?

useEffect函数中的第二个参数丢失,这意味着每次呈现组件时都会调用 REST API。应提供空数组作为第二个参数,以便仅在第一次渲染时调用 REST API:

React.useEffect(() => {
  axios
    .get(`https://jsonplaceholder.typicode.com/posts/${id}`)
    .then(...)
    .catch(...);
}, []);

第 10 章:与 GraphQLAPI 的交互

  1. 在 GitHub GraphQL 资源管理器中,创建一个查询以返回 React 项目中最后五个打开的问题。在响应中返回问题标题和 URL:
query { 
  repository (owner:"facebook", name:"react") {
    issues(last: 5, states:[OPEN]) {
      edges {
        node {
          title
          url
        }
      }
    }
  }
}
  1. 增强最后一个查询,将返回的问题数设置为一个参数,并将此默认值设置为 5:
query ($lastCount: Int = 5) { 
  repository (owner:"facebook", name:"react") {
    issues(last: $lastCount, states: [OPEN]) {
      edges {
        node {
          title
          url
        }
      }
    }
  }
}
  1. 在 GitHub GraphQL 资源管理器中创建一个变体,以取消星型存储库的启动。变异应将存储库 ID 作为参数:
mutation ($repoId: ID!) {
  removeStar(input: { starrableId: $repoId }) {
    starrable {
      stargazers {
        totalCount
      }
    }
  }
}
  1. GraphQL 查询进入 HTTP 请求的哪一部分?

HTTP 主体

  1. GraphQL 变异进入 HTTP 请求的哪一部分?

HTTP 主体

  1. 如何使react-apollo``Query组件类型的响应安全?

创建另一个扩展Query的组件,将结果的类型作为泛型参数传入:

class MyQuery extends Query<IResult> {}

然后我们可以在 JSX 中使用MyQuery组件。

  1. 当您使用react-boost构建项目时,默认情况下是启用还是禁用缓存?

在…上

  1. 我们可以在Mutation组件上使用什么道具来更新本地缓存?

update道具。

第 11 章:使用 Jest 的单元测试

  1. 假设我们正在实现一个 Jest 测试,我们有一个名为result的变量,我们要检查它不是null。我们如何使用 Jest matcher 函数实现这一点?
expect(result).not.toBeNull()
  1. 假设我们有一个名为person的变量,类型为IPerson
interface IPerson {
  id: number;
  name: string;
}

我们要检查person变量是否为{ id: 1, name: "bob" }。我们如何使用 Jest matcher 函数实现这一点?

expect(person).not.toBeEqual({ id: 1, name: "bob" });
  1. 是否可以使用 Jest 快照测试执行最后一个问题中的检查?如果是,怎么做?

对:

expect(person).toMatchSnapshot();
  1. 我们已经实现了一个名为CheckList的组件,它从列表中的数组中呈现文本。每个列表项都有一个复选框,以便用户可以选择列表项。该组件有一个名为onItemSelect的函数 prop,当用户选中复选框选择一个项目时调用该函数 prop。我们正在进行一项测试,以验证onItemSelect道具是否有效。以下代码行在测试中呈现组件:
const { container } = render(<SimpleList data={["Apple", "Banana", "Strawberry"]} onItemSelect={handleListItemSelect} />);

我们如何为handleListItemSelect使用 Jest 模拟函数并检查它是否被调用?

const handleListItemSelect = jest.fn();
const { container } = render(<SimpleList data={["Apple", "Banana", "Strawberry"]} onItemSelect={handleListItemSelect} />);

// TODO - select the list item

expect(handleListItemSelect).toBeCalledTimes(1);
  1. 在上一个问题SimpleList的实现中,onItemSelect函数引入了一个名为item的参数,该参数是用户选择的string值。在我们的测试中,假设我们已经模拟了用户选择"Banana"。如何检查调用了onItemSelect函数,而项目参数为"Banana"
expect(handleListItemSelect).toBeCalledWith("Banana");
  1. 在最后两个问题的SimpleList实现中,文本使用一个标签显示,该标签与复选框绑定,并使用for属性。我们如何使用 React 测试库中的函数首先定位"Banana"复选框,然后再进行检查?
const checkbox = getByLabelText("Banana") as HTMLInputElement;
fireEvent.change(checkbox, {
  target: { checked: true }
});
  1. 在本章中,我们发现呈现来自 JSONPlaceholder REST API 的帖子的代码覆盖率很低。当我们从 RESTAPI 获取帖子时,componentDidMount函数中处理 HTTP 错误代码是其中一个未涉及的领域。创建一个测试以覆盖此代码区域:
test("When the post GET request errors when the page is loaded, an error is shown", async () => {
  const mock = new MockAdapter(axios);
  mock.onGet("https://jsonplaceholder.typicode.com/posts").reply(404);

  const { getByTestId } = render(<App />);

  const error: any = await waitForElement(() => getByTestId("error"));

  expect(error).toMatchSnapshot();
});

需要在App组件代码中添加测试 ID:

jsx {this.state.error && <p className="error" data-testid="error">{this.state.error}</p>}****