继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

React+TypeScript项目实战入门教程

慕勒3428872
关注TA
已关注
手记 228
粉丝 13
获赞 51
概述

本文详细介绍了如何在React项目中使用TypeScript进行开发,涵盖了从创建项目到实战应用的全过程。通过实例讲解了如何创建简单的待办事项列表,并通过React Context和Redux管理组件间的状态。此外,还介绍了如何使用Jest和React Testing Library进行测试,以及如何部署和上线项目。

React基础知识回顾
React组件基础

React 是一个用于构建用户界面的开源JavaScript库。它由Facebook维护,用于构建可预测的、可重用的UI组件。React组件是构成应用程序的基本单元,可以分为类组件和函数组件两种类型。

类组件

类组件是使用ES6的类来定义的组件,它继承自React的Component类。类组件可以访问生命周期方法,也可以使用状态和上下文。

import React, { Component } from 'react';

class MyComponent extends Component {
    render() {
        return <div>Hello, World!</div>;
    }
}

函数组件

函数组件是使用函数来定义的组件,它不需要访问生命周期方法或状态。函数组件通常用于简单的显示逻辑。

import React from 'react';

const MyComponent = () => {
    return <div>Hello, World!</div>;
};
React生命周期介绍

React组件的生命周期可以分为三个阶段:挂载(Mounting)、更新(Updating)和卸载(Unmounting)。每个阶段都有若干生命周期方法,用于在不同阶段执行特定的操作。

挂载阶段

  • constructor: 初始化状态和属性。
  • static getDerivedStateFromProps: 根据props更新状态。
  • render: 返回要渲染的元素。
  • componentDidMount: 组件挂载后的操作,通常用于异步数据获取。
class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { name: 'React' };
    }

    componentDidMount() {
        console.log('Component mounted');
    }

    render() {
        return <div>{this.state.name}</div>;
    }
}

更新阶段

  • static getDerivedStateFromProps: 根据新的props更新状态。
  • shouldComponentUpdate: 决定组件是否需要更新。
  • getSnapshotBeforeUpdate: 在DOM更新之前获取快照。
  • componentDidUpdate: 组件更新后的操作。
class MyComponent extends Component {
    state = { message: 'Hello, World!' };

    componentDidMount() {
        console.log('Component mounted');
    }

    componentDidUpdate() {
        console.log('Component updated');
    }

    render() {
        return <div>{this.state.message}</div>;
    }
}

卸载阶段

  • static getDerivedStateFromProps: 组件卸载前的最后机会更新状态。
  • componentWillUnmount: 组件卸载前的操作,通常用于清理订阅或定时器。
class MyComponent extends Component {
    componentDidMount() {
        console.log('Component mounted');
    }

    componentWillUnmount() {
        console.log('Component will unmount');
    }

    render() {
        return <div>Goodbye, World!</div>;
    }
}
状态管理和事件处理

状态

状态是组件内部的可变数据,用于存储组件的状态。状态通常在constructor中初始化,并通过setState方法更新。

class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
    }

    incrementCount = () => {
        this.setState({ count: this.state.count + 1 });
    }

    render() {
        return (
            <div>
                <p>Count: {this.state.count}</p>
                <button onClick={this.incrementCount}>Increment</button>
            </div>
        );
    }
}

事件处理

React使用合成事件系统来处理事件,这些事件的行为类似于浏览器事件,但具有更好的跨浏览器一致性。

class MyComponent extends Component {
    handleButtonClick = (event) => {
        console.log('Button clicked');
        console.log(event.target); // 输出点击的按钮
    }

    render() {
        return (
            <button onClick={this.handleButtonClick}>
                Click me
            </button>
        );
    }
}
TypeScript基础入门
TypeScript简介

TypeScript是JavaScript的一个超集,它在JavaScript的基础上增加了类型系统,使得开发者可以编写更安全、更易于维护的代码。TypeScript代码可以通过编译器编译成JavaScript代码,也可以直接运行在支持ES6的现代浏览器中。

TypeScript的主要特性包括:

  • 类型注解:为变量、函数参数、返回值等添加类型注解。
  • 接口:定义对象的结构。
  • 泛型:定义可变类型的函数或类。
  • 命名空间:组织代码和避免命名冲突。
变量类型声明与接口

在TypeScript中,可以通过类型注解来为变量、函数参数、返回值等添加类型。

let name: string = 'Alice';
let age: number = 25;
let isStudent: boolean = true;

function add(a: number, b: number): number {
    return a + b;
}

接口

接口用于定义对象的结构,可以用来约束对象的属性和方法。

interface User {
    name: string;
    age: number;
    greet(): void;
}

class Student implements User {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}
函数类型与泛型

函数类型

TypeScript可以为函数的参数和返回值定义类型。

function add(a: number, b: number): number {
    return a + b;
}

function logInfo<T>(item: T): void {
    console.log(item);
}

logInfo<number>(42); // 输出 42

泛型

泛型是一种可以使用任意类型的机制。泛型可以定义可变类型的函数或类。

function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>('Hello, World!'); // 输出 Hello, World!
React项目中引入TypeScript
创建React+TypeScript项目

创建一个新的React+TypeScript项目可以使用create-react-app工具,它提供了一个脚手架,简化了项目的创建过程。

npx create-react-app my-app --template typescript
cd my-app
npm start

配置TypeScript在React项目中的使用

create-react-app已经为TypeScript配置好了项目结构和编译配置。tsconfig.json文件包含了TypeScript编译器的配置选项,而babel.config.js文件包含了Babel的配置,用于将TypeScript代码编译成ES5 JavaScript。

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2015",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "react",
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "*": ["src/*"]
    },
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src"]
}
// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-typescript',
    '@babel/preset-react'
  ],
  plugins: ['@babel/plugin-transform-runtime'],
};

安装和配置相关依赖

确保项目中安装了最新的TypeScript和相关依赖。

npm install typescript @types/react @types/react-dom @types/jest

@types/react@types/react-dom提供了React和ReactDOM的类型定义。

React+TypeScript项目实战
创建简单的待办事项列表

创建一个简单的待办事项列表组件,该组件允许用户添加和删除待办事项。

添加待办事项

import React, { useState } from 'react';

function TodoList() {
    const [todos, setTodos] = useState<string[]>([]);

    const addTodo = (text: string) => {
        setTodos([...todos, text]);
    };

    const removeTodo = (index: number) => {
        setTodos(todos.filter((_, i) => i !== index));
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Add a todo"
                onKeyDown={(e) => {
                    if (e.key === 'Enter') {
                        addTodo(e.target.value);
                        e.target.value = '';
                    }
                }}
            />
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo}{' '}
                        <button onClick={() => removeTodo(index)}>Remove</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;

使用TypeScript增强React组件

通过添加类型注解来增强组件,确保组件的输入和输出具有正确的类型。

import React from 'react';
import { useState } from 'react';

interface TodoListProps {}

interface TodoListState {
    todos: string[];
}

const TodoList: React.FC<TodoListProps> = () => {
    const [todos, setTodos] = useState<string[]>([]);

    const addTodo = (text: string) => {
        setTodos([...todos, text]);
    };

    const removeTodo = (index: number) => {
        setTodos(todos.filter((_, i) => i !== index));
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Add a todo"
                onKeyDown={(e) => {
                    if (e.key === 'Enter') {
                        addTodo(e.target.value);
                        e.target.value = '';
                    }
                }}
            />
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo}{' '}
                        <button onClick={() => removeTodo(index)}>Remove</button>
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default TodoList;
组件间通信与状态管理

使用React Context

React Context提供了一种在组件树中传递数据的方式,而无需手动将prop从顶层组件传递到每个组件。

import React, { createContext, useState, useContext } from 'react';

interface Todo {
    text: string;
}

const TodosContext = createContext<Todo[]>([]);

function TodosProvider({ children }) {
    const [todos, setTodos] = useState<Todo[]>([]);

    const addTodo = (text: string) => {
        setTodos([...todos, { text }]);
    };

    const removeTodo = (index: number) => {
        setTodos(todos.filter((_, i) => i !== index));
    };

    return (
        <TodosContext.Provider value={{ todos, addTodo, removeTodo }}>
            {children}
        </TodosContext.Provider>
    );
}

function useTodos() {
    return useContext(TodosContext);
}

function TodoList() {
    const { todos, addTodo, removeTodo } = useTodos();

    return (
        <div>
            <input
                type="text"
                placeholder="Add a todo"
                onKeyDown={(e) => {
                    if (e.key === 'Enter') {
                        addTodo(e.target.value);
                        e.target.value = '';
                    }
                }}
            />
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo.text}{' '}
                        <button onClick={() => removeTodo(index)}>Remove</button>
                    </li>
                ))}
            </ul>
            <TodosProvider>
                <TodoList />
            </TodosProvider>
        </div>
    );
}

export default TodoList;

使用Redux管理状态

Redux是一个用于管理应用状态的库,它可以与React无缝集成,以实现状态的集中管理。

import { createStore } from 'redux';
import { Todo, TodoAction } from './types';

const initialState: Todo[] = [];

const reducer = (state: Todo[] = initialState, action: TodoAction): Todo[] => {
    switch (action.type) {
        case 'ADD_TODO':
            return [...state, action.payload];
        case 'REMOVE_TODO':
            return state.filter((_, i) => i !== action.payload.index);
        default:
            return state;
    }
};

const store = createStore(reducer);

function TodoList() {
    const [todos, setTodos] = useState<Todo[]>(store.getState());

    useEffect(() => {
        const unsubscribe = store.subscribe(() => {
            setTodos(store.getState());
        });
        return () => unsubscribe();
    }, []);

    const addTodo = (text: string) => {
        store.dispatch({ type: 'ADD_TODO', payload: { text } });
    };

    const removeTodo = (index: number) => {
        store.dispatch({ type: 'REMOVE_TODO', payload: { index } });
    };

    return (
        <div>
            <input
                type="text"
                placeholder="Add a todo"
                onKeyDown={(e) => {
                    if (e.key === 'Enter') {
                        addTodo(e.target.value);
                        e.target.value = '';
                    }
                }}
            />
            <ul>
                {todos.map((todo, index) => (
                    <li key={index}>
                        {todo.text}{' '}
                        <button onClick={() => removeTodo(index)}>Remove</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default TodoList;
// types.ts
export interface Todo {
    text: string;
    index: number;
}

export interface TodoAction {
    type: 'ADD_TODO' | 'REMOVE_TODO';
    payload: {
        text: string;
        index: number;
    };
}
// reducer.ts
const initialState: Todo[] = [];

const todosReducer = (state: Todo[] = initialState, action: TodoAction): Todo[] => {
    switch (action.type) {
        case 'ADD_TODO':
            return [...state, { ...action.payload }];
        case 'REMOVE_TODO':
            return state.filter(todo => todo.index !== action.payload.index);
        default:
            return state;
    }
};
项目测试与调试
使用Jest和React Testing Library进行测试

Jest是一个JavaScript测试框架,React Testing Library是一个用于编写React组件测试的库。它们可以一起使用,以更自然的方式测试React组件。

安装依赖

npm install --save-dev jest @testing-library/react @testing-library/jest-dom

编写测试

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import TodoList from './TodoList';

test('renders todo list', () => {
    render(<TodoList />);
    const inputElement = screen.getByPlaceholderText('Add a todo');
    expect(inputElement).toBeInTheDocument();
});

test('adds a new todo', () => {
    render(<TodoList />);
    const inputElement = screen.getByPlaceholderText('Add a todo');
    fireEvent.change(inputElement, { target: { value: 'New todo' } });
    fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter', charCode: 13 });
    expect(screen.getByText('New todo')).toBeInTheDocument();
});

test('removes a todo', () => {
    render(<TodoList />);
    const inputElement = screen.getByPlaceholderText('Add a todo');
    fireEvent.change(inputElement, { target: { value: 'New todo' } });
    fireEvent.keyDown(inputElement, { key: 'Enter', code: 'Enter', charCode: 13 });
    fireEvent.click(screen.getByText('Remove'));
    expect(screen.queryByText('New todo')).not.toBeInTheDocument();
});

整合TypeScript和测试库

确保项目中安装了TypeScript的类型定义。

npm install --save-dev @types/jest @types/testing-library__dom

解决常见调试问题

  • 使用console.logconsole.error输出调试信息。
  • 使用浏览器的调试工具查看组件的渲染情况。
  • 使用Jest的断言和匹配器进行断言失败时的调试。
// 在组件中添加调试信息
console.log('Component rendered');

// 在测试中添加调试信息
console.log('Test started');
项目部署与上线
使用npm和webpack构建项目

create-react-app已经配置好了webpack和Babel,可以直接使用npm run build命令生成生产环境的代码。

npm run build
项目部署到GitHub Pages或其他服务器

将构建好的文件上传到GitHub Pages或其他服务器。

部署到GitHub Pages

  1. 配置package.json文件中的homepage字段。
  2. 将构建好的文件放到gh-pages分支。
  3. 使用GitHub Pages发布站点。
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "https://username.github.io/my-app",
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1"
  },
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  },
  "devDependencies": {
    "gh-pages": "^4.0.0"
  }
}
npm run deploy

部署到其他服务器

  1. 使用scp或类似工具将构建好的文件上传到服务器。
  2. 配置服务器的web服务器(如Nginx或Apache)来提供静态文件。
scp -r build username@server:/path/to/public
持续集成与持续部署简介

持续集成和持续部署(CI/CD)是指将代码自动集成到主分支,并自动部署到生产环境的过程。常见的CI/CD工具包括Jenkins、GitHub Actions、GitLab CI和Travis CI。

GitHub Actions示例

  1. 创建.github/workflows目录。
  2. 创建一个ci.yml文件,配置CI/CD流程。
name: CI/CD

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Install dependencies
      run: npm ci
    - name: Build
      run: npm run build
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        deploy_key: ${{ secrets.GITHUB_DEPLOY_KEY }}
        target_branch: main
        build_dir: build

通过上述步骤,可以实现自动化的CI/CD流程,确保代码的质量和部署的一致性。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP