美文网首页
TypeScript

TypeScript

作者: 朝曦 | 来源:发表于2019-10-14 01:43 被阅读0次

    有状态组件

    当我们的组件需要根据用户的输入更新的时候,我们需要有状态的组件。
    深入理解React的有状态组件的最佳实践超出了本文的讨论范围,但是我们可以快速看一下给我们得到Hello组件加上状态之后是什么样子。我们将渲染两个<button>来更新Hello组件显示的感叹号的数量。
    要做到这一点,我们需要做:

    1. 为状态定义一个类型(如:this.state
    2. 根据我们在构造函数中给出的props来初始化this.state
    3. 为我们的按钮创建两个事件处理程序(onIncrementonDecrement)。
    // src/components/StatefulHello.tsx
    
    import * as React from "react";
    
    export interface Props {
      name: string;
      enthusiasmLevel?: number;
    }
    
    interface State {
      currentEnthusiasm: number;
    }
    
    class Hello extends React.Component<Props, State> {
      constructor(props: Props) {
        super(props);
        this.state = { currentEnthusiasm: props.enthusiasmLevel || 1 };
      }
    
      onIncrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm + 1);
      onDecrement = () => this.updateEnthusiasm(this.state.currentEnthusiasm - 1);
    
      render() {
        const { name } = this.props;
    
        if (this.state.currentEnthusiasm <= 0) {
          throw new Error('You could be a little more enthusiastic. :D');
        }
    
        return (
          <div className="hello">
            <div className="greeting">
              Hello {name + getExclamationMarks(this.state.currentEnthusiasm)}
            </div>
            <button onClick={this.onDecrement}>-</button>
            <button onClick={this.onIncrement}>+</button>
          </div>
        );
      }
    
      updateEnthusiasm(currentEnthusiasm: number) {
        this.setState({ currentEnthusiasm });
      }
    }
    
    export default Hello;
    
    function getExclamationMarks(numChars: number) {
      return Array(numChars + 1).join('!');
    }
    

    说明:

    1. 像props一样,我们需要为state定义一个新的类型:State
    2. 使用this.setState更新React中的state
    3. 使用箭头函数初始化方法类(如:onIncrement = () => ...

    加入样式

    src/components/Hello.css新建css文件:

    .hello {
      text-align: center;
      margin: 20px;
      font-size: 48px;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }
    
    .hello button {
      margin-left: 25px;
      margin-right: 25px;
      font-size: 40px;
      min-width: 50px;
    }
    

    create-react-app使用的Webpack和loaders等工具允许我们引入stylesheets文件。当执行run的时候,引入的.css文件将被编译到输出文件中。因此在src/components/Hello.tsx中加入引入:

    import './Hello.css';
    

    用Jest写测试

    我们可以根据我们设想的组件的功能,为组件编写测试。
    首先需要安装Enzyme及其相关依赖。Enzyme是React生态系统中的常用工具,可以更轻松地编写组件行为方式的测试。

    npm install -D enzyme jest-cli @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16 react-test-renderer
    

    (译者注:原文中没有安装jest-cli,运行测试时会报错,此处已加上)
    在编写测试之前,我们需要使用React16的适配器对Enzyme进行配置。创建文件src/setupTests.ts, 该配置文件在运行测试时自动加载:

    import * as enzyme from 'enzyme';
    import * as Adapter from 'enzyme-adapter-react-16';
    
    enzyme.configure({ adapter: new Adapter() });
    

    现在,可以开始写测试文件了。新建文件src/components/Hello.test.tsx,与被测试的Hello.tsx在同一目录下。

    // src/components/Hello.test.tsx
    
    import * as React from 'react';
    import * as enzyme from 'enzyme';
    import Hello from './Hello';
    
    it('renders the correct text when no enthusiasm level is given', () => {
      const hello = enzyme.shallow(<Hello name='Daniel' />);
      expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
    });
    
    it('renders the correct text with an explicit enthusiasm of 1', () => {
      const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={1}/>);
      expect(hello.find(".greeting").text()).toEqual('Hello Daniel!')
    });
    
    it('renders the correct text with an explicit enthusiasm level of 5', () => {
      const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
      expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
    });
    
    it('throws when the enthusiasm level is 0', () => {
      expect(() => {
        enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={0} />);
      }).toThrow();
    });
    
    it('throws when the enthusiasm level is negative', () => {
      expect(() => {
        enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={-1} />);
      }).toThrow();
    });
    

    运行测试:npm run test

    测试报错.png

    添加状态管理

    通过redux对组件进行状态管理。

    安装

    npm install -S redux react-redux @types/react-redux
    

    定义应用的状态

    我们需要定义Redux存储的状态的形式。因此,新建文件src/types/index.tsx,该文件包含可能在整个程序中用到的类型的定义。

    // src/types/index.tsx
    
    export interface StoreState {
        languageName: string;
        enthusiasmLevel: number;
    }
    

    我们的目的是:languageName将是这个应用程序编写的编程语言(即TypeScript或JavaScript),而enthusiasmLevel有所不同。当我们编写第一个container时,就会理解为什么要故意让state和props不同。

    添加actions

    让我们从创建一组消息类型开始,我们的应用程序可以在src / constants / index.tsx中响应。

    // src/constants/index.tsx
    
    export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
    export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;
    
    
    export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
    export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;
    

    这种const / type模式允许我们以易于访问和可重构的方式使用TypeScript的字符串文字类型。
    接下来,我们将创建一组可以在src / actions / index.tsx中创建这些操作的操作和函数。

    import * as constants from '../constants';
    
    export interface IncrementEnthusiasm {
        type: constants.INCREMENT_ENTHUSIASM;
    }
    
    export interface DecrementEnthusiasm {
        type: constants.DECREMENT_ENTHUSIASM;
    }
    
    export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;
    
    export function incrementEnthusiasm(): IncrementEnthusiasm {
        return {
            type: constants.INCREMENT_ENTHUSIASM
        }
    }
    
    export function decrementEnthusiasm(): DecrementEnthusiasm {
        return {
            type: constants.DECREMENT_ENTHUSIASM
        }
    }
    

    我们创建了两个类型,用以描述increment actions和decrement actions看起来是什么样子。我们还创建了一个类型(EnthusiasmAction)来描述一个action可以是 increment还是decrement的情况。最后,我们创建了两个函数来实际执行我们可以使用的acions。

    添加一个reducer

    reducer是一个通过创建应用的state的副本,来产生变化的函数,并且没有副作用。
    reducer文件为src/reducers/index.tsx。它的功能是确保increments 将enthusiasm level提高1,而decrements 将enthusiasm level降低1,但enthusiasm level不低于1。

    // src/reducers/index.tsx
    
    import { EnthusiasmAction } from '../actions';
    import { StoreState } from '../types/index';
    import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';
    
    export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
      switch (action.type) {
        case INCREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
        case DECREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
      }
      return state;
    }
    

    创建一个container

    使用Redux,我们经常会编写组件和容器。组件通常与数据无关,并且主要在表示级别工作。容器通常包装组件并向其提供显示和修改状态所需的任何数据。
    首先,更新src / components / Hello.tsx,以便可以修改状态。我们将为onIncrementonDecrementProps添加两个可选的回调属性:
    首先,修改src/components/Hello.tsx,使它可以修改状态。为onIncrement和onDecrement的Props添加两个可选的回调属性:

    export interface Props {
      name: string;
      enthusiasmLevel?: number;
      onIncrement?: () => void;
      onDecrement?: () => void;
    }
    

    然后将这两个回调函数绑定到在组件中添加的两个按钮上:

    function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
      if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
      }
    
      return (
        <div className="hello">
          <div className="greeting">
            Hello {name + getExclamationMarks(enthusiasmLevel)}
          </div>
          <div>
            <button onClick={onDecrement}>-</button>
            <button onClick={onIncrement}>+</button>
          </div>
        </div>
      );
    }
    

    接下来可以把组件包装成一个容器(container)了。首先创建文件src/containers/Hello.tsx,并导入:

    import Hello from '../components/Hello';
    import * as actions from '../actions/';
    import { StoreState } from '../types/index';
    import { connect, Dispatch } from 'react-redux';
    

    react-redux的connect函数将能够将Hello组件转换为容器,通过以下mapStateToPropsmapDispatchToProps这两个函数实现:

    export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
      return {
        enthusiasmLevel,
        name: languageName,
      }
    }
    
    export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
      return {
        onIncrement: () => dispatch(actions.incrementEnthusiasm()),
        onDecrement: () => dispatch(actions.decrementEnthusiasm()),
      }
    }
    

    最后,调用connectconnect首先获取mapStateToPropsmapDispatchToProps,然后返回另一个用来包装组件的函数。生成的容器使用以下代码行定义:

    export default connect(mapStateToProps, mapDispatchToProps)(Hello);
    

    创建store

    回到src / index.tsx。我们需要创建一个具有初始状态的store,并使用所有的reducer进行设置。

    import { createStore } from 'redux';
    import { enthusiasm } from './reducers/index';
    import { StoreState } from './types/index';
    
    const store = createStore<StoreState>(enthusiasm, {
      enthusiasmLevel: 1,
      languageName: 'TypeScript',
    });
    

    接下来,把./src/components/Hello与./src/containers/Hello交换使用,并使用react-reduxProviderpropscontainer连接起来。导入这些并且将store传递给Provider的属性:

    import Hello from './containers/Hello';
    import { Provider } from 'react-redux';
    
    ReactDOM.render(
      <Provider store={store}>
        <Hello />
      </Provider>,
      document.getElementById('root') as HTMLElement
    );
    

    相关文章

      网友评论

          本文标题:TypeScript

          本文链接:https://www.haomeiwen.com/subject/vimrlqtx.html