1 Star 0 Fork 0

acproject/learning_react_ts

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

使用React Hooks 与 Typescript进行开发(自用笔记,不保证正确)

通过npx创建第一个react项目

# 第一步
npx create-react-app --template typescript trello-clone

# 第二步
yarn && yarn build

# 第三步, 查看效果
yarn start

改造为EMP项目 (Option)

改造为EMP项目(可选项, 不想关注可以跳过的章节)

新建一个样式的ts文件 src/styles.ts

具体内容如下:

import React from 'react';
import styled from 'styled-components';
// 采用react的方式来定义CSS
export const buttonStyles :React.CSSProperties = {
    backgroundColor: "#5aac44",
    borderRadius: "3px",
    border: "none",
    boxShadow: "none"
}

// 下面采用styled-components定义
export const AppContainer = styled.div`
    align-items: flex-start;
    background-color: #3179ba;
    display: flex;
    flex-direction: row;
    height: 100%;
    padding: 20px;
    width: 100%;
`

export const Button = styled.button`
    background-color: #5aac44;
    border-radius: 3px;
    border: none;
    box-shadow: none;
`

export const ColumnContainer = styled.div`
    background-color: #ebecf0;
    width: 300px;
    min-height: 40px;
    margin-right: 20px;
    border-radius: 3px;
    padding: 8px 8px;
    flex-grow: 0;
`

export const ColumnTitle = styled.div`
    padding: 6px 16px 12px;
    font-weight: bold;
`

export const CardContainer = styled.div`
    background-color: #fff;
    cursor: pointer;
    margin-bottom: 0.5rem;
    padding:  0.5rem 1rem;
    max-width: 300px;
    border-radius: 3px;
    box-shadow: #091e4240 0px 1px 0px 0px;
`

通过styled-components模块进行样式组建的编写需要下面的语句进行安装依赖:

yarn add styled-components

# 安装ts需要的对应的types
yarn add @types/styled-components

修改 src/App.tsx文件

原来的文件如下:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

修改为:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { AppContainer } from './styles'

const App = () => {
  return (
    <AppContainer>
      可以拖动的列
    </AppContainer>
  );
}

export default App;

通过 yarn start测试修改后的节目

先看一下React的使用类组件的方式:

import React from 'react';

interface CounterProps {
    message: string;
  
}
interface CounterState {
    count: number;
}

class Counter extends React.Component<CounterProps, CounterState> {
    state : CounterState = {
        count:0
    };

    render(): React.ReactNode {
        return (
            <div>
                {this.props.message} {this.state.count}
            </div>
        );
    }
}

export default Counter;

React.Component需要接收两个参数,一个props和state,如果你i定义的是函数组件就不需要这么麻烦,如下:

export const Example = () => {
  return <div>函数组件</div>
}

Typescript会自动推断出类型为JSX,如果你想明确是React函数组件,可以这样定义:

export const Example:React.FC = () => {
  return <div>React函数组件</div>
}

串联所有组件的样式

需要的串联的组件有, 详细内容见 src/styles.ts:

  • AppContainer
  • ColumnContainer
  • ColumnTitle
  • CardContaner

创建Column组件

  1. 我们需要创建 src/Column.tsx文件,并以函数组件方式定义,内容如下:
import React from "react";
export const Column = () => {
    return <div>列容器的标题</div>
}
  1. 引入我们之前定义的style的组件
import React from "react";
import {ColumnContainer, ColumnTitle} from './styles';
export const Column = () => {
    // return <div>列容器的标题</div>
    return (
        <ColumnContainer>
            <ColumnTitle>列容器的标题</ColumnTitle>
        </ColumnContainer>
    )
}
  1. 如果想要给自定义函数组件添加props,我们需要这样进行操作
import React from "react";
import {ColumnContainer, ColumnTitle} from './styles';

type ColumnProps = {
    text: string
}


export const Column = ({text}: ColumnProps) => {
    // return <div>列容器的标题</div>
    return (
        <ColumnContainer>
            <ColumnTitle>列容器的标题</ColumnTitle>
        </ColumnContainer>
    )
}
  1. 如果我们有一个另外的组件是上面Column的组件的子组件,我们该如何定义呢?,其实也很简单,如下定义子组件:
import React from "react";
import {ColumnContainer, ColumnTitle} from './styles';

type ColumnProps  = {
    text?: string | undefined
}


//children == type PropsWithChildren<P> = P & { children?: ReactNode | undefined };
export const Column = ({text, children}: React.PropsWithChildren<ColumnProps>) => {
    // return <div>列容器的标题</div>
    return (
        <ColumnContainer>
            <ColumnTitle>{text}</ColumnTitle>
            { /**这里children是由react负责去传递*/}
            {children}
        </ColumnContainer>
    )
}
  1. 之前定义的body部分抽取出来定义一个新的组件为Card,内容如下:
import React from "react";

import {CardContainer} from './styles';

interface CardProps {
    text?: string| undefined
}

export const Card = ({text}: CardProps) => {
    return <CardContainer>{text}</CardContainer>
}
  1. 我们将上面的内容串联起来,修改 src/App.tsx
import React from 'react';
import './App.css';
import { AppContainer } from './styles'
import {Card} from './Card'
import {Column} from './Column'

const App = () => {
  return (

      <AppContainer>
        <Column text='To Do'>
          <Card text="去买一杯咖啡" />
        </Column>
        <Column text='修改Bug'>
          <Card text='需要修改React中出现的问题'/>
        </Column>
        <Column text='已经编写完成'>
          <Card text='工作计划已将编写完成'/>
        </Column>
      </AppContainer>
  
  );
}

export default App;

准备新的组件,并通过react的hooks实现State与事件的集成

编写新的自定义组件

修改 src/styles.ts文件

interface AddItemButtonProps  {
    dark?: boolean
}

export const AddItemButton = styled.button<AddItemButtonProps>`
    background-color: #ffffff3d;
    border-radius: 3px;
    border: none;
    color: ${props = > (props.dark ? "#000" : "#fff")};
    cursor: pointer;
    max-width: 300px;
    padding: 10px 12px;
    text-align: left;
    transition: background 85ms ease-in;
    width: 100%;
    &:hover {
        background-color: #ffffff52;
    }
`

export const NewItemFormContainer = styled.div`
    max-width: 300px;
    display: flex;
    flex-direction: column;
    width: 100%;
    align-items: flex-start;
`


export const NewItemButton = styled.button`
    background-color: #5aac44;
    border-radius: 3px;
    border: none;
    box-shadow: none;
    color: #fff;
    padding: 6px 12px;
    text-align: center;
`

export const NewItemInput = styled.input`
    border-radius:3px;
    border: none;
    box-shadow: #091e4240 0px 1px 0px 0px;
    margin-bottom: 0.5rem;
    padding: 0.5rem 1rem;
    width: 100%;
`

创建带有State的组件

import React,{useState} from "react";
import { NewItemForm } from "./NewItemForm";
import { AddItemButton } from './styles';

interface AddNewItemProps {
    onAdd(text:string) :void
    toggleButtonText:string
    dark?: boolean | undefined
}

export const AddNewItem = (props: AddNewItemProps) => {
    const [showForm, setShowForm] = useState(false);
    const {onAdd, toggleButtonText, dark} = props;

    if (showForm) {
        return (
            <NewItemForm 
            onAdd={text => {
                onAdd(text)
                setShowForm(false)
            }} />
        )
    }

    return (
        <AddItemButton dark={dark} onClick={() => setShowForm(true)}>
            {toggleButtonText}
        </AddItemButton>
    )
}

将组建添加到 src/Column.tsx组件中去,代码如下:

<AppContainer>
        <Column text='To Do'>
          <Card text="去买一杯咖啡" />
        </Column>
        <Column text='修改Bug'>
          <Card text='需要修改React中出现的问题'/>
        </Column>
        <Column text='已经编写完成'>
          <Card text='工作计划已将编写完成'/>
        </Column>
        <AddNewItem toggleButtonText="新增一列" onAdd={console.log} />
      </AppContainer>

创建自动聚焦的Input自定义组件,通过useRef这个hook来实现

定义自己的Hooks,在 src文件夹中创建一个新的 utils文件夹,并新建文件 useFocus.ts

import {useRef, useEffect} from 'react';

// Refs提供了一种被React渲染的实际DOM节点的方法
export const useFocus = () => {
    const ref = useRef<HTMLInputElement>(null)

    useEffect(() => {
        ref.current?.focus()
    })

    return ref;
}

回到之前的 NewItemForm.tsx文件中,并引入上面的 useFocus.ts, 修改部分如下:

const inputRef = useFocus();

    return (
        <NewItemFormContainer>
            <NewItemInput 
                value={text} 
                onChange={e => setText(e.target.value)} 
                ref={inputRef}
            />
            <NewItemButton onClick={() => onAdd(text)}>创建新的表单</NewItemButton>
        </NewItemFormContainer>
    )

添加全局状态和业务逻辑

使用useReducer这个hooks来管理复杂的状态

App.tsx中创建Reducer的hooks,测试代码如下:

type State = {count:number}
type Action = {type: "increment"} | {type: "decrement"}

const App = () => {
  const counterReducer = (state:State, action:Action) => {
    switch(action.type) {
      case 'increment':
        return {count: state.count + 1}
        case 'decrement':
          return {count : state.count - 1}
        default:
          throw new Error()
    }
  }

  const [state, dispatch] = useReducer(counterReducer, {count: 0})
  return (
<>
 <p>Count: {state.count}</p>
 <button onClick={()=> dispatch({type: "decrement"})}>-</button>
 <button onClick={()=> dispatch({type: "increment"})}>+</button>
</>
)

创建AppState的类型

通常React应用程序中,数据通过props自上而下(父到子)传递,但对于应用程序中许多组件需要在组件之间共享一些数据,而不必在每个层级显示传递一个prop,这时我们采用Context进行共享一个组件树的Prop。让我们在 src文件夹内部创建 AppStateContext.tsx

import React, {createContext} from "react";

type Task = {
    id:string
    text:string
}

type List = {
    id: string
    text: string
    tasks: Task[]
}

export type AppState = {
    lists: List[]
}

type AppStateContextProps =  {
    state: AppState
}

const AppStateContext = createContext<AppStateContextProps>({} as AppStateContextProps)

export const AppStateProvider = ({children}: React.PropsWithChildren<{}>) => {
    return (
        <AppStateContext.Provider value={{state: appData}}>
                {children}

        </AppStateContext.Provider>
    )
}

const appData: AppState = {
    lists: [
        {id:"0", text:"To Do", tasks: [{id: "c0", text: "去买一杯咖啡"}]},
        {id:"1", text:"修改Bug", tasks: [{id: "c2", text: "需要修改React中出现的问题"}]},
        {id:"2", text:"已经编写完成", tasks: [{id: "c3", text: "工作计划已将编写完成"}]}  
    ]
}

用AppStateProvier包装原有App组件

src文件夹中创建 index.tsx,内容如下:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AppStateProvider } from './AppStateContext'

ReactDOM.render(
  <AppStateProvider>
    <App />
  </AppStateProvider>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

继续修改 AppStateContext.tsx,使用全局的上下文数据

import React, {createContext, useReducer, useContext} from "react";
export const useAppState = () => {
    return useContext(AppStateContext)
}

修改 App.tsx,替换已经常量为 AppStateContext.tsx的数据,并修改 Column.tsx

// AppStateContext.tsx 
import { useAppState } from './AppStateContext';
const { state } = useAppState()
  return (
    {state.lists.map((list, i) => (
          <Column text={list.text} key={list.id} index={i}/>
        ))}

        <AddNewItem toggleButtonText="新增一列" onAdd={console.log} />

        <p>Count: {state2.count}</p>
        <button onClick={()=> dispatch({type: "decrement"})}>-</button>
        <button onClick={()=> dispatch({type: "increment"})}>+</button>
  )
// Column.tsx
import { AddNewItem } from "./AddNewItem";
import { useAppState } from "./AppStateContext";
import { Card } from "./Card";
import {ColumnContainer, ColumnTitle} from './styles';

type ColumnProps  = {
    text?: string | undefined
    index: number
}
export const Column = ({text,index}: ColumnProps) => {
    // return <div>列容器的标题</div>
    const {state} = useAppState()
    return (
        <ColumnContainer>
            <ColumnTitle>{text}</ColumnTitle>
            {state.lists[index].tasks.map(task => (
                <Card text={task.text} key={task.id} />
            ))}
  
            <AddNewItem toggleButtonText="增加新的任务" onAdd={console.log} dark />
        </ColumnContainer>
    )
}

定义Action和Reducer

修改 AppStateContext.tsx文件,并修改 AppStateContextProps的类型定义

// 定义Action
type Action =
    {
        type: "ADD_LIST",
        payload: string,
    } |
    {
        type: "ADD_TASK",
        payload: { text: string; taskId: string }
    } |
    {
        type: "MOVE_LIST",
        payload: {
            dragIndex: number
            hoverIndex: number
        }
    }

    type AppStateContextProps = {
    state: AppState,
    dispatch: React.Dispatch<Action> // new
   
     // 手工定义假数据
    const appData: AppState = {
    lists: [
        { id: "0", text: "To Do", tasks: [{ id: "c0", text: "去买一杯咖啡" }] },
        { id: "1", text: "修改Bug", tasks: [{ id: "c2", text: "需要修改React中出现的问题" }] },
        { id: "2", text: "已经编写完成", tasks: [{ id: "c3", text: "工作计划已将编写完成" }] }
    ]
}

/**
 * 用Reducer代替,上面定义的AppState,让组件按照Action来进行调用
 */
const appStateReducer = (state: AppState, action: Action): AppState => {
    switch (action.type) {

        case "ADD_LIST": {
            const visibilityExample = "可见的"
            return {
                ...state,
                lists: [
                    ...state.lists, {
                        id: uuidv4(),
                        text: action.payload,
                        tasks: []
                    }
                ]
            }
        }
        case "ADD_TASK": {
            const visibilityExample = "可见的"
            return { ...state }
        }

        case "MOVE_LIST": {
        
        }
        default: {
            return state
        }
    }
}

export const AppStateProvider = ({ children }: React.PropsWithChildren<{}>) => {

    const [state, dispatch] = useReducer(appStateReducer, appData)

    return (
        <AppStateContext.Provider value={{ state, dispatch }}>
            {children}

        </AppStateContext.Provider>
    )
}

添加可以拖动的组件库

yarn add react-dnd react-dnd-html5-backend

创建Dnd提供器

修改index.tsx文件,增加Dnd组件

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AppStateProvider } from './AppStateContext'
import { DndProvider, } from 'react-dnd';
import { HTML5Backend as Backend } from 'react-dnd-html5-backend'

ReactDOM.render(
    <DndProvider backend={Backend}>
        <AppStateProvider>
            <App />
        </AppStateProvider>
    </DndProvider>,
    document.getElementById('root')
);

通过Action存储拖后的数据保存到State中

要实现这个功能,要添加Action,所以要修改AppStateContext.tsx文件中的Action部分

注意: react-dnd的hooks只能回调当前拖动的项目数据,如果我们创建一个拖动预览,看起来就像是在复制一个组件,为了修复这个问题我们需要隐藏当前的组件,所有我们需要知道我们拖动的是什么内容(组件),它到底是卡片还是列。我能需要通过ID来确定。这里我们将使用useDraguseDrop两个hooks来完成组件的拖放操作,创建一个新的文件ColumnDragItem.ts来实现拖的动作

export interface ColumnDragItem {
    index: number
    id: string
    text: string
    type: "COLUMN"
}

export type DragItem = ColumnDragItem;
import { DragItem } from "./ColumnDragItem";

type Action =  |
    {
        type: "SET_DRAGGED_ITEM"
        payload: DragItem | undefined
    }

定义useItemDragHook

这里可以新建一个自定义的Hook,它将返回一个可以接受拖动元素的拖动方法,每当开始拖动的时候都会发送SET_DRAG_ITEM操作,当停止拖动时,将在未定义的有效payload的情况下再次发送此操作。这里创建一个新文件useItemDrag.ts

import { useDrag } from 'react-dnd';
import { useAppState } from './AppStateContext';
import { DragItem } from './DragItem';



export const useItemDrag = (item: DragItem) => {
    const { dispatch } = useAppState()
    const [, drag] = useDrag({
        type: "SET_DRAGGED_ITEM",
        item,
        end: () => dispatch({ type: "SET_DRAGGED_ITEM", payload: undefined }),
    })
    return { drag }
}

**注意:**新版本的react-dnd是没有begin属性的

将被拖动的组件进行隐藏


MIT License Copyright (c) 2022 acproject Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

学习Typescript和React的笔记 展开 收起
README
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/acproject_admin/learning_react_ts.git
git@gitee.com:acproject_admin/learning_react_ts.git
acproject_admin
learning_react_ts
learning_react_ts
master

搜索帮助