zoukankan      html  css  js  c++  java
  • 使用 TypeScript 来开发 React 的注意事项

    npx create-react-app ts-with-react --typescript
    

    npx 将 npm 安装的模块安装到临时的一个目录,等待整个模块安装完成以后就会删了。比如我们创建一个 react 的项目,一般很久才创建一次,所以这种时候我们一般都用 npx 而不是 npm。
    npx 还会调用项目内部安装的模块

    一开始是这个样子:

    import React from 'react'
    
    const Hello = (props: any) => {
    	return <h2>{props.message}</h2>
    }
    
    export default Hello
    

    然后我们就要定义我们传入的 props 是什么样子的:

    import React from 'react'
    interface IHelloProps {
    	message: string
    }
    
    // 这样做有两个好处,一个呢是自动补全,还有一个呢是传入类型的地方要是传入的不是 string 的话就会报错,这个在 vscode 的 TypescriptValidate = true 这里一定要开,自己在这里踩坑踩了很久
    const Hello = (props: IHelloProps) => {
    	return <h2>{props.message}</h2>
    }
    export default Hello
    

    但是这种写法还是不够好,我们最好是按照下面的这样来写:

    import React from 'react'
    interface IHelloProps {
    	message: string
    }
    
    // 这个时候还有了一个 children 属性,还获得了一些静态属性
    const Hello: React.FunctionComponent<IHelloProps> = (props) => {
    	return <h2>{props.message}</h2>
    }
    
    Hello.defaultProps = {
    	message: "Hello World"
    }
    export default Hello
    

    我们来试试用 useState,我们再来看看 useEffect, useEffect 也是一样的

    import React, { useState } from 'react'
    
    // 在这里我们用了 props,所以泛型就不用传了
    // 倘若传了 props,我们就用泛型来约束类型
    const LikeButton: React.FC = () => {
    	const [like, setLike] = useState(0)
    	return (
    		<button onClick={() => setLike(like + 1)}>
    			{like}
    		</button>
    	)
    }
    

    自定义 Hook

    import React, { useState, useEffect } from 'react'
    const useMousePosition = () => {
    	const [ positions, setPositions ] = useState({x: 0, y: 0})
    	useEffect(() => {
    		console.log('add effect', positions.x)
    		const updateMouse = (e: MouseEvent) => {
    			setPositions({ x: e.clientX, y: e.clientY })
    		}
    		document.addEventListener('mousemove', updateMouse)
    		return () => {
    			console.log('remove effect', positions.x)
    			document.removeEventListener('mousemove', updateMouse)
    		}
    	}, [])
    	return positions
    }
    

    重头戏来了

    我们如何使用 TS 来写 react 的组件
    无状态组件
    我们在某些情况下会使用到无状态组件(也就是一个函数),这个无状态组件函数使用 TypeScript 来定义几乎与 JavaScript 很像,如:

    import * as React from "react";
    
    const TestPage: React.SFC = () => {
      return (
        <div>
          this is test page.
        </div>
      );
    };
    
    export default TestPage;
    

    当我们需要传递 Props 时,只用定义一个 Props 接口,然后给 props 指明类型:

    export interface IHeaderProps {
      localImageSrc: string;
      onLineImageSrc: string;
    }
    
    export const Header: React.SFC<IHeaderProps> = (props: IHeaderProps) => {
      const { localImageSrc, onLineImageSrc } = props;
      return (
        <div className={styles["header-container"]}>
          <img src={localImageSrc} />
          <img src={onLineImageSrc} />
        </div>
      );
    };
    

    有状态组件
    假设当我们需要使用到一个有状态的组件,如:因为某些操作(onClick)来改变 state时,我们需要给 state 定义一个接口,与上述的 props 类似,在编写有状态组件时,需要给 React.Component的范型传递你的类型:

    export interface IHomePageState {
      name: string;
    }
    
    class HomeComponent extends React.Component<{}, IHomePageState> {
      constructor(props: {}) {
        super(props);
        this.state = {
          name: "",
        };
      }
    
      public setName = () => {
        this.setState({
          name: "icepy",
        });
      }
      
      public render(){
        const { name } = this.state;
        return (
          <div>
             <div onClick={this.setName}> set name </div>
             <div>{name}</div>
          </div>
        )
      }
    }
    

    Props & State 组件
    对于另外的一些需求,可能我们设计的组件是一个容器或者是什么别的,总之它既有Props又有State,其实从上述的有状态组件中,我们可以很清晰的察觉到 React.Component 第一个参数传的就是 Props 的类型,因此,当我们要使用 Props & State 组件时,就要如此:

    export interface IHomePageState {
      name: string;
    }
    
    export interface IHomePageProps {
      home: string;
    }
    
    class HomeComponent extends React.Component<IHomePageProps, IHomePageState> {
      constructor(props: IHomePageProps) {
        super(props);
        this.state = {
          name: "",
        };
      }
    
      public setName = () => {
        this.setState({
          name: "icepy",
        });
      }
      
      public render(){
        const { name } = this.state;
        const { home } = this.props;
        return (
          <div>
             <div onClick={this.setName}> set name </div>
             <div>{name} {home}</div>
          </div>
        )
      }
    }
    

    Router 组件
    当我们存在有多个页面时,就会用到 react-router-dom 路由库,因此在类型安全上,我们需要为我们的 Props 继承上 React-Router 的 Props,才能让编译通过。与上述的 Props & State 组件类似,我们要为我们定义的接口 IHomePageProps 继承 RouteComponentProps,如:

    import { RouteComponentProps } from "react-router-dom";
    
    export interface IHomePageProps extends RouteComponentProps<any>{
      home: string;
    }
    
    export interface IHomePageProps {
      home: string;
    }
    
    class HomeComponent extends React.Component<IHomePageProps, IHomePageState> {
      constructor(props: IHomePageProps) {
        super(props);
        this.state = {
          name: "",
        };
      }
    
      public setName = () => {
        this.setState({
          name: "icepy",
        });
      }
      
      public render(){
        const { name } = this.state;
        const { home } = this.props;
        return (
          <div>
             <div onClick={this.setName}> set name </div>
             <div>{name} {home}</div>
          </div>
        )
      }
    }
    

    页面级别的 Reducers
    在我们度过了前面的几个组件之后,可能你的项目会越来越复杂,因此我们会使用到 Redux 来管理我们 React 应用的数据流,页面级别的 Reducers ,顾名思义,这是我们关联在页面容器组件里的 Action,通过这些 Action 和 Props 的结合,方便的管理数据流。
    这些 Action 会分为 同步 Action 和 异步 Action,这也是我们为什么会用到 redux-thunk 的原因。
    首先,我们来为类型安全定义接口:

    // page 
    
    import { Dispatch } from "redux";
    import { RouteComponentProps } from "react-router-dom";
    
    export interface IHomePageActionsProps {
      dataSync: () => void;
      dataAsync: (parameter: string) => (dispatch: Dispatch) => void;
    }
    
    export interface IHomePageProps extends RouteComponentProps<any>, IHomePageActionsProps {
      homePage: IHomePageStoreState;
    }
    
    export interface IHomePageStoreState {
      syncId: string;
      asyncId: string;
    }
    
    // global dir 
    export interface IStoreState {
      homePage: IHomePageStoreState;
    }
    

    然后定义一个 mapStateToProps 函数(没有用装饰器的原因是让你能阅读明白):

    const mapStateToProps = (state: IStoreState) => {
      const { homePage } = state;
      return {
        homePage,
      };
    };
    

    分别定义 Action 和 Reducers:

    // action
    import * as CONST from "./constants";
    import { Dispatch } from "redux";
    
    export function dataSync() {
      const syncData  = {
        type: CONST.SYNC_DATA,
        payload: {
          data: "syncId=https://github.com/icepy",
        },
      };
      return syncData;
    }
    
    export function dataAsync(parameter: string): (dispatch: Dispatch) => void {
      return (dispatch: Dispatch) => {
        const asyncData = {
          type: CONST.ASYNC_DATA,
          payload: {
            data: "asyncId=https://icepy.me",
          },
        };
        setTimeout(() => {
          dispatch(asyncData);
        }, 2000);
      };
    }
    
    // reducers
    import { IAction } from "@/global/types";
    import * as CONST from "./constants";
    import * as TYPES from "./types";
    
    const initState: TYPES.IHomePageStoreState = {
      syncId: "默认值",
      asyncId: "默认值",
    };
    
    export function homeReducers(state = initState, action: IAction): TYPES.IHomePageStoreState {
      const { type, payload } = action;
      switch (type) {
        case CONST.SYNC_DATA:
          return { ...state, syncId: payload.data };
        case CONST.ASYNC_DATA:
          return { ...state, asyncId: payload.data };
        default:
          return { ...state };
      }
    }
    

    在 Store 中 引入我们的 reducers,因为我们已经为 state 定义了类型,因此我们可以很方便的关联上,并且知道哪里有错误:

    import { createStore, applyMiddleware, combineReducers, compose } from "redux";
    import thunk from "redux-thunk";
    import { homeReducers } from "@/pages/Home/flow/homeReducers";
    
    /* eslint-disable no-underscore-dangle, no-undef */
    const composeEnhancers = (window as any) && (window as any).REDUX_DEVTOOLS_EXTENSION_COMPOSE || compose;
    const reducer = combineReducers({
      homePage: homeReducers,
    });
    
    export const configureStore = () => createStore(
      reducer,
      composeEnhancers(applyMiddleware(thunk)),
    );
    

    最后,我们使用 connect 函数将这些关联起来:

    class HomeComponent extends React.Component<TYPES.IHomePageProps, TYPES.IHomePageState> {
       ... 省略 可自行访问 [WLM-TypeScript-React-Starter] 项目
    }
    
    export const HomePage = connect(mapStateToProps, actions)(HomeComponent);
    

    Global级别的 Reducers
    global 顾名思义,这是一种可以全局访问的 reducers ,我们要做的事情也和页面级别 reducers 非常类似,定义好 state 的接口,然后将 global 在 Store 中配置正确,如:

    import { createStore, applyMiddleware, combineReducers, compose } from "redux";
    import thunk from "redux-thunk";
    import { homeReducers } from "@/pages/Home/flow/homeReducers";
    import { globalReducers } from "./reducers";
    
    /* eslint-disable no-underscore-dangle, no-undef */
    const composeEnhancers = (window as any) && (window as any).REDUX_DEVTOOLS_EXTENSION_COMPOSE || compose;
    const reducer = combineReducers({
      global: globalReducers,
      homePage: homeReducers,
    });
    
    export const configureStore = () => createStore(
      reducer,
      composeEnhancers(applyMiddleware(thunk)),
    );
    

    当我们需要访问 global 时,有两种方式:

    1. 在 mapStateToProps 函数中将 global 返回给页面级别的 Props
    2. 随意的调用 global 中的 Action ,只是需要手动的将 dispatch 函数传递给这些 Action
    import * as React from "react";
    import { Dispatch } from "redux";
    import { connect } from "react-redux";
    import { HashRouter as Router, Route, NavLink } from "react-router-dom";
    import { IStoreState } from "./global/types";
    import * as globalActions from "./global/actions";
    import { HomePage } from "./pages/Home";
    import { TestPage } from "./pages/TestPage";
    import "./style.less";
    
    interface IAppComponentProps {
      dispatch: Dispatch;
    }
    
    class AppComponent extends React.Component<IAppComponentProps> {
      constructor(props: IAppComponentProps) {
        super(props);
        globalActions.setGlobalSyncId(this.props.dispatch);
      }
    
      public render() {
        return (
          <Router >
            <div>
              <div className="nav-container">
                <NavLink to="/" >Home Page</NavLink>
                <NavLink to="/test">Test Page</NavLink>
              </div>
              <Route exact={true} path="/" component={HomePage} />
              <Route path="/test" component={TestPage} />
            </div>
          </Router>
        );
      }
    }
    
    const mapStateToProps = (state: IStoreState) => {
      const { global } = state;
      return {
        global,
      };
    };
    
    export const App = connect(mapStateToProps)(AppComponent);
    

    到此为止,我们的这些组件使用,还不够为一个复杂的 React 应用“服务”,因为我们还需要一些额外的配置,如:tslint,editorconfig,local assets 的处理,yarn,pre-commit 等等,这些额外的集成为多人协作的复杂项目开了一个好头,因此,我们还需要进一步的去处理这些配置,如 tslint:

    {
      "extends": ["tslint:recommended", "tslint-react"],
      "rules": {
          "jsx-alignment": true,
          "jsx-wrap-multiline": true,
          "jsx-self-close": true,
          "jsx-space-before-trailing-slash": true,
          "jsx-curly-spacing": "always",
          "jsx-boolean-value": false,
          "jsx-no-multiline-js": false,
          "object-literal-sort-keys": false,
          "ordered-imports": false,
          "no-implicit-dependencies": false,
          "no-submodule-imports": false,
          "no-var-requires": false
      }
    }
    

    总结
    在使用 TypeScript 和 React 的过程中积累了不少经验,但还有一些使用的技巧没有介绍到,这就需要我们在之后的过程中去慢慢摸索了。

    前端/Typescript

  • 相关阅读:
    如何将网页变成灰色的.
    页面中的传参介质(ViewState,Session,Items),Dictionary的用法.
    CSS控制Table单元格强制换行与强制不换行
    网页效果大集合
    结合ashx来在DataGrid中显示从数据库中读出的图片
    Atlas学习手记(29):JavaScript面向对象的扩展(三):接口Interface
    FormView中EditItemTemplate的注意事项
    javascript基本和常用的东西
    eclipse X11 xming 打造window下的linux c++开发环境
    转一篇软件测试方面的思考小文
  • 原文地址:https://www.cnblogs.com/ssaylo/p/14489262.html
Copyright © 2011-2022 走看看