MYW
2 years ago
46 changed files with 29774 additions and 0 deletions
-
13TypeScript/01.TS常用类型/16.typeof运算符.ts
-
44TypeScript/02.TS高级类型/02.类型兼容性/02.函数类型兼容性(参数个数和类型).ts
-
20TypeScript/02.TS高级类型/02.类型兼容性/03.函数类型兼容性(返回值类型).ts
-
21TypeScript/02.TS高级类型/02.类型兼容性/04.交叉类型.ts
-
17TypeScript/02.TS高级类型/03.泛型/01.泛型.ts
-
32TypeScript/02.TS高级类型/03.泛型/02.泛型约束.ts
-
16TypeScript/02.TS高级类型/03.泛型/03.多个泛型变量情况.ts
-
14TypeScript/02.TS高级类型/03.泛型/04.泛型接口.ts
-
24TypeScript/02.TS高级类型/03.泛型/05.泛型类.ts
-
18TypeScript/02.TS高级类型/03.泛型/06.泛型工具类型(Partial).ts
-
17TypeScript/02.TS高级类型/03.泛型/07.泛型工具类型(Readonly).ts
-
10TypeScript/02.TS高级类型/03.泛型/08.泛型工具类型(Pick).ts
-
16TypeScript/02.TS高级类型/03.泛型/09.泛型工具类型(Record).ts
-
21TypeScript/02.TS高级类型/04.索引签名类型.ts
-
20TypeScript/02.TS高级类型/05.映射类型.ts
-
21TypeScript/02.TS高级类型/06.索引查询类型.ts
-
26TypeScript/03.React中使用TS/01.函数组件的类型.tsx
-
27TypeScript/03.React中使用TS/02.函数默认值.tsx
-
37TypeScript/03.React中使用TS/03.事件绑定和事件对象.tsx
-
23TypeScript/03.React中使用TS/04.组件类型.tsx
-
27TypeScript/03.React中使用TS/05.组件的属性.tsx
-
33TypeScript/03.React中使用TS/06.组件状态和事件.tsx
-
23TypeScript/04.任务列表案例/todo_ts/.gitignore
-
46TypeScript/04.任务列表案例/todo_ts/README.md
-
28310TypeScript/04.任务列表案例/todo_ts/package-lock.json
-
43TypeScript/04.任务列表案例/todo_ts/package.json
-
BINTypeScript/04.任务列表案例/todo_ts/public/favicon.ico
-
43TypeScript/04.任务列表案例/todo_ts/public/index.html
-
BINTypeScript/04.任务列表案例/todo_ts/public/logo192.png
-
BINTypeScript/04.任务列表案例/todo_ts/public/logo512.png
-
25TypeScript/04.任务列表案例/todo_ts/public/manifest.json
-
3TypeScript/04.任务列表案例/todo_ts/public/robots.txt
-
2TypeScript/04.任务列表案例/todo_ts/src/App.css
-
69TypeScript/04.任务列表案例/todo_ts/src/App.tsx
-
63TypeScript/04.任务列表案例/todo_ts/src/components/TodoAdd.tsx
-
29TypeScript/04.任务列表案例/todo_ts/src/components/TodoFooter.tsx
-
35TypeScript/04.任务列表案例/todo_ts/src/components/TodoList.tsx
-
141TypeScript/04.任务列表案例/todo_ts/src/css/todos-base.css
-
379TypeScript/04.任务列表案例/todo_ts/src/css/todos-index.css
-
10TypeScript/04.任务列表案例/todo_ts/src/index.tsx
-
1TypeScript/04.任务列表案例/todo_ts/src/logo.svg
-
1TypeScript/04.任务列表案例/todo_ts/src/react-app-env.d.ts
-
15TypeScript/04.任务列表案例/todo_ts/src/reportWebVitals.ts
-
5TypeScript/04.任务列表案例/todo_ts/src/setupTests.ts
-
8TypeScript/04.任务列表案例/todo_ts/src/todos.d.ts
-
26TypeScript/04.任务列表案例/todo_ts/tsconfig.json
@ -0,0 +1,44 @@ |
|||||
|
// 1.参数个数:参数少的可以赋值给参数多的
|
||||
|
|
||||
|
type F1 = (a: number) => void |
||||
|
type F2 = (a: number, b: number) => void |
||||
|
|
||||
|
let f1: F1 |
||||
|
let f2: F2 |
||||
|
|
||||
|
// 参数少的 f1 可以赋值给参数多的 f2 。 例: f2 = f1
|
||||
|
// 但是参数多的不能赋值给参数少的
|
||||
|
|
||||
|
|
||||
|
|
||||
|
// 2.参数类型:相同位置的参数类型要相同兼容
|
||||
|
|
||||
|
// 参数类型是原始类型
|
||||
|
type F3 = (a: number) => void |
||||
|
type F4 = (a: number) => void |
||||
|
|
||||
|
let f3: F3 |
||||
|
let f4: F4 |
||||
|
|
||||
|
// 相同位置和相同类型的参数,可以参数少的可以赋值给参数多的,也可以参数多的赋值给参数少的。 例:f3 = f4 f4 = f3
|
||||
|
|
||||
|
|
||||
|
// 参数类型是对象类型
|
||||
|
interface Point2D { |
||||
|
x: number |
||||
|
y: number |
||||
|
} |
||||
|
|
||||
|
interface Point3D { |
||||
|
x: number |
||||
|
y: number |
||||
|
z: number |
||||
|
} |
||||
|
|
||||
|
type F5 = (p: Point2D) => void // 相当于有两个参数
|
||||
|
type F6 = (p: Point3D) => void // 相当于有三个参数
|
||||
|
|
||||
|
let f5: F5 |
||||
|
let f6: F6 |
||||
|
|
||||
|
// 参数少的可以赋值给参数多的,但是参数多的不可以赋值给参数少的
|
@ -0,0 +1,20 @@ |
|||||
|
// 返回值类型:只关注返回值类型本身
|
||||
|
|
||||
|
// 返回值类型是原始类型:
|
||||
|
type F5 = () => string |
||||
|
type F6 = () => string |
||||
|
|
||||
|
let f5: F5 |
||||
|
let f6: F6 |
||||
|
|
||||
|
// 返回值类型一样,f5 和 f6 可以相互赋值。 例: f6 = f5 f5 = f6
|
||||
|
|
||||
|
|
||||
|
// 返回值类型是对象类型:
|
||||
|
type F7 = () => { name: string } |
||||
|
type F8 = () => { name: string; age: number } |
||||
|
|
||||
|
let f7: F7 |
||||
|
let f8: F8 |
||||
|
|
||||
|
// 成员多的赋值给成员少的 f7 = f8
|
@ -0,0 +1,21 @@ |
|||||
|
// 交叉类型:功能类似 extends,用于组合多个类型为一个类型(常用于对象类型)
|
||||
|
|
||||
|
interface Person { |
||||
|
name: string |
||||
|
say(): number |
||||
|
} |
||||
|
|
||||
|
interface Contact { |
||||
|
phone: string |
||||
|
} |
||||
|
|
||||
|
// PersonDetail 同时具有 Person 和 Contact 的属性和方法
|
||||
|
type PersonDetail = Person & Contact |
||||
|
|
||||
|
let obj: PersonDetail = { |
||||
|
name: 'jack', |
||||
|
phone: '123456', |
||||
|
say() { |
||||
|
return 1 |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
// 使用泛型创建一个函数
|
||||
|
|
||||
|
function id<Type>(value: Type) : Type { // <Type> 相当于是一个占位符
|
||||
|
return value |
||||
|
} |
||||
|
|
||||
|
// 调用泛型函数:
|
||||
|
// 1.以 number 类型调用泛型函数
|
||||
|
const num = id<number>(10) |
||||
|
|
||||
|
// 2.以 string 类型调用泛型函数
|
||||
|
const str = id<string>('a') |
||||
|
const ret = id<boolean>(false) |
||||
|
|
||||
|
// 泛型简写
|
||||
|
let num1 = id(100) // number类型
|
||||
|
let str1 = id('abc') // string类型
|
@ -0,0 +1,32 @@ |
|||||
|
// 泛型约束的两种方式:1.指定更加具体的类型 2.添加约束
|
||||
|
|
||||
|
function id<Type>(value: Type) : Type { |
||||
|
// Type 是表示任意类型的,导致 value 没办法访问任何属性和方法
|
||||
|
// console.log(value.length) // length 会报错
|
||||
|
return value |
||||
|
} |
||||
|
|
||||
|
// 第一种:指定更加具体的类型
|
||||
|
// 将类型修改为 Type[] ( Type 类型的数组 ),因为只要是数组就一定存在 length 属性,所以可以访问
|
||||
|
function id2<Type>(value: Type[]) : Type[] { |
||||
|
console.log(value.length) |
||||
|
return value |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 第二种:添加约束
|
||||
|
interface ILength { length: number } |
||||
|
|
||||
|
// 这里的 extends 不是继承,而是表示 Type 要满足 ILength 中 length 属性的约束
|
||||
|
function id3 <Type extends ILength> (value: Type) : Type { |
||||
|
value.length |
||||
|
return value |
||||
|
} |
||||
|
|
||||
|
// 只要满足 length 属性就可以
|
||||
|
id3(['a', 'x']) |
||||
|
id3('abc') |
||||
|
id3({length: 10, name: 'jack'}) |
||||
|
|
||||
|
// 错误演示
|
||||
|
// id3(123)
|
@ -0,0 +1,16 @@ |
|||||
|
// 第二个类型变量受第一个类型变量约束
|
||||
|
|
||||
|
// Key 受 Type 约束(Key 要满足 Type 中的属性)
|
||||
|
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { |
||||
|
return obj[key] |
||||
|
} |
||||
|
|
||||
|
getProp({ name: 'jack', age: 20}, 'age') // 第二个参数要满足第一个参数的属性,可以是 age 或 name
|
||||
|
getProp({ name: 'jack', age: 20}, 'name') |
||||
|
|
||||
|
// 补充
|
||||
|
getProp(20, 'toFixed') // 20 是 number 类型,toFixed 是 number 类型能够访问的方法
|
||||
|
getProp('abc', 'split') |
||||
|
getProp('abc', 1) // 1表示索引,字符串像一个数组一样,可以通过数组的索引的方式来访问
|
||||
|
getProp(['a'], length) |
||||
|
getProp(['a'], 1000) |
@ -0,0 +1,14 @@ |
|||||
|
interface IdFunc<Type> { |
||||
|
id: (balue: Type) => Type |
||||
|
ids: () => Type[] |
||||
|
} |
||||
|
|
||||
|
// 需要指定显示类型
|
||||
|
let obj: IdFunc<number> = { // 指定显示的类型为 number
|
||||
|
id(value) { |
||||
|
return value |
||||
|
}, |
||||
|
ids() { |
||||
|
return [1, 3, 5] |
||||
|
} |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
// 第一种
|
||||
|
class GenericNumber<NumType> { |
||||
|
defaultValue: NumType |
||||
|
add: (x: NumType, y: NumType) => NumType |
||||
|
} |
||||
|
|
||||
|
// 明确指定 <类型>
|
||||
|
const myNum = new GenericNumber<number>() |
||||
|
myNum.defaultValue = 10 |
||||
|
|
||||
|
|
||||
|
// 第二种
|
||||
|
class GenericNumber2<NumType> { |
||||
|
defaultValue: NumType |
||||
|
add: (x: NumType, y: NumType) => NumType |
||||
|
|
||||
|
constructor(value: NumType) { |
||||
|
this.defaultValue = value |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 省略类型
|
||||
|
const myNum2 = new GenericNumber() |
||||
|
myNum.defaultValue = 10 |
@ -0,0 +1,18 @@ |
|||||
|
// 泛型工具类型:Partial<Type> 用来构造(创建)一个类型,将 Type 的所有属性设置为可选
|
||||
|
|
||||
|
interface Props { |
||||
|
id: string |
||||
|
children: number[] |
||||
|
} |
||||
|
|
||||
|
// 使用 泛型工具 Partial
|
||||
|
type PartialProps = Partial<Props> |
||||
|
|
||||
|
// 没有用泛型工具,属性是必加的
|
||||
|
let p1: Props = { |
||||
|
id: '', |
||||
|
children: [1] |
||||
|
} |
||||
|
|
||||
|
// 加入 PartialProps 后,属性可加可不加
|
||||
|
let p2: PartialProps = {} |
@ -0,0 +1,17 @@ |
|||||
|
// 泛型工具类型:Readonly<Type> 将 Type 的所有属性都设置为 readonly(只读)
|
||||
|
|
||||
|
interface Props { |
||||
|
id: string |
||||
|
children: number[] |
||||
|
} |
||||
|
|
||||
|
type ReadonlyProps = Readonly<Props> |
||||
|
|
||||
|
// 所有属性都是只读
|
||||
|
let p1: ReadonlyProps = { |
||||
|
id: '1', |
||||
|
children: [1, 3] |
||||
|
} |
||||
|
|
||||
|
// 属性是只读,不能修改
|
||||
|
// p1.id = '2'
|
@ -0,0 +1,10 @@ |
|||||
|
// 泛型工具类型:Pick<Type, Keys> 从 Type 中选择一组属性来构造新类型
|
||||
|
|
||||
|
interface Props{ |
||||
|
id: string |
||||
|
title: string |
||||
|
children: number[] |
||||
|
} |
||||
|
|
||||
|
// 从 Props 中选择两个属性给 PickProps
|
||||
|
type PickProps = Pick<Props, 'id' | 'title'> // PickProps 具有 Props 的 id 和 title 属性
|
@ -0,0 +1,16 @@ |
|||||
|
// 泛型工具类型:Record<Keys, type> 构造一个对象类型,属性键为 Keys, 属性类型为 Type
|
||||
|
|
||||
|
type RecordObj = Record< 'a' | 'b' | 'c', string[]> // 表示属性键 a、b、c 都是 string 类型的数组
|
||||
|
|
||||
|
let obj: RecordObj = { |
||||
|
a: ['a'], |
||||
|
b: ['b'], |
||||
|
c: ['c'] |
||||
|
} |
||||
|
|
||||
|
// 不使用工具类型 Record
|
||||
|
type RecordObj2 = { |
||||
|
a2: string[] |
||||
|
b2: string[] |
||||
|
c2: string[] |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
// 索引签名类型
|
||||
|
// 使用场景:无法确定对象中有哪些属性(或者对象中出现任意多个属性) 例:用户输入的内容
|
||||
|
|
||||
|
interface AnyObject { |
||||
|
// 字符串的键都可以出现在 AnyObject 对象中
|
||||
|
[key: string] : number // key 表示一个占位符(可以更改名字),number 表示属性的值的类型
|
||||
|
} |
||||
|
|
||||
|
let obj: AnyObject = { |
||||
|
a: 1, |
||||
|
abc: 123, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 在数组中使用
|
||||
|
interface MyArray<Type> { |
||||
|
[index: number]: Type |
||||
|
} |
||||
|
|
||||
|
let arr1 : MyArray<number> = [1, 3, 5] |
||||
|
arr1[0] |
@ -0,0 +1,20 @@ |
|||||
|
// 映射类型:基于旧类型创建新类型
|
||||
|
|
||||
|
type PropKeys = 'x' | 'y' | 'z' // 联合类型
|
||||
|
type Type1 = { x: number; y: number; z: number } |
||||
|
|
||||
|
// 使用映射类型进行简化
|
||||
|
type Type2 = { [Key in PropKeys]: number } // key 表示 PropKeys 联合类型中的任意一个
|
||||
|
|
||||
|
// 映射类型只能在类型别名中使用,不能在接口中使用
|
||||
|
// 错误演示:
|
||||
|
/* |
||||
|
* interface Type3 { |
||||
|
* [Key in PropKeys]: number |
||||
|
* } |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
// 根据对象类型创建新类型
|
||||
|
type Props = { a: number; b: string; c: boolean } |
||||
|
type Type3 = { [key in keyof Props]: number } // key in 表示 key 可以是 Props 中所有个键名称中的任意一个,类型为 number
|
@ -0,0 +1,21 @@ |
|||||
|
type Props = { a: number; b: string; c: boolean } |
||||
|
|
||||
|
// 查看 Props 中 a 的类型
|
||||
|
type TypeA = Props['a'] |
||||
|
|
||||
|
// 模拟 Partial 类型:
|
||||
|
type MyPartial<T> = { |
||||
|
[P in keyof T]?: T[P] // p 相当于 key
|
||||
|
} |
||||
|
|
||||
|
type PartialProps = MyPartial<Props> |
||||
|
|
||||
|
|
||||
|
// 索引其它查询方式:同时查询多个索引的类型
|
||||
|
type TypeB = Props['a' | 'b'] |
||||
|
|
||||
|
type Props2 = { a: number; b: number; c: boolean } |
||||
|
|
||||
|
type TypeC = Props2['a' | 'b'] |
||||
|
// a 和 b 类型都是 number,TypeD 只显示一个 number,实现去重功能
|
||||
|
type TypeD = Props2[keyof Props] |
@ -0,0 +1,26 @@ |
|||||
|
import ReactDOM from "react-dom"; |
||||
|
import { FC } from "react"; |
||||
|
|
||||
|
// React 函数组件的类型以及组件的属性
|
||||
|
|
||||
|
type Props = { name: string; age?: number} |
||||
|
|
||||
|
// 第一种:
|
||||
|
const Hello: FC<Props> = ( { name, age } ) => ( |
||||
|
<div>我是:{name}, 我 { age } 岁</div> |
||||
|
) |
||||
|
|
||||
|
// 第二种(简化):
|
||||
|
const HI = ({ name, age }: Props) => ( |
||||
|
<div> |
||||
|
我是:{name}, 我 { age } 岁 |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
const App = () => <div> |
||||
|
{/* name 是必填的 */} |
||||
|
<Hello name="jack" age={ 30 }/> |
||||
|
<HI name="Ken" age={ 20 }/> |
||||
|
</div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,27 @@ |
|||||
|
import ReactDOM from "react-dom"; |
||||
|
|
||||
|
// 函数的默认值
|
||||
|
|
||||
|
type Props = { name: string; age?: number} |
||||
|
|
||||
|
// const Hello: FC<Props> = ( { name, age } ) => (
|
||||
|
// <div>我是:{name}, 我 { age } 岁</div>
|
||||
|
// )
|
||||
|
|
||||
|
// 提供默认属性
|
||||
|
// Hello.defaultProps = {
|
||||
|
// age: 18 // age 是可选属性,当没给 age 的值时,默认 age 的值为 18
|
||||
|
// }
|
||||
|
|
||||
|
|
||||
|
// 简化写法
|
||||
|
const Hello = ( { name, age = 18 }: Props ) => ( // 给 age 添加默认值 18
|
||||
|
<div>我是:{name}, 我 { age } 岁</div> |
||||
|
) |
||||
|
|
||||
|
const App = () => <div> |
||||
|
{/* name 是必填的 */} |
||||
|
<Hello name="jack" /> |
||||
|
</div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,37 @@ |
|||||
|
import React from "react"; |
||||
|
import ReactDOM from "react-dom"; |
||||
|
|
||||
|
// 事件绑定和事件对象
|
||||
|
|
||||
|
type Props = { name: string; age?: number} |
||||
|
|
||||
|
const Hello = ( { name, age = 18 }: Props ) => { |
||||
|
// const onClick = () => {
|
||||
|
// console.log('赞!')
|
||||
|
// }
|
||||
|
|
||||
|
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => { |
||||
|
console.log('赞!', e.currentTarget) |
||||
|
} |
||||
|
|
||||
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
|
console.log(e.target.value) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
我是:{name}, 我 { age } 岁 |
||||
|
<button onClick={onClick}>点赞</button> |
||||
|
<input onChange={onChange} /> |
||||
|
{/* 可以利用 TS 的类型推论来查看事件对象类型(将鼠标放在 e 上可以查看) */} |
||||
|
{/* <input onChange={ e => {} } /> */} |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const App = () => <div> |
||||
|
{/* name 是必填的 */} |
||||
|
<Hello name="jack" /> |
||||
|
</div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,23 @@ |
|||||
|
import React from "react"; |
||||
|
import ReactDOM from "react-dom"; |
||||
|
|
||||
|
// 组件的类型
|
||||
|
|
||||
|
type State = { count: number} |
||||
|
type Props = { message?: string} |
||||
|
|
||||
|
// 无 props, 无 state
|
||||
|
class C1 extends React.Component {} |
||||
|
|
||||
|
// 有 props,无 state
|
||||
|
class C2 extends React.Component<Props> {} |
||||
|
|
||||
|
// 无 props,有 state
|
||||
|
class C3 extends React.Component<{}, State> {} |
||||
|
|
||||
|
// 有 props,有 state
|
||||
|
class C4 extends React.Component<Props, State> {} |
||||
|
|
||||
|
const App = () => <div></div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,27 @@ |
|||||
|
import React from "react"; |
||||
|
import ReactDOM from "react-dom"; |
||||
|
|
||||
|
// 组件的属性
|
||||
|
|
||||
|
type Props = { name: string; age?: number } |
||||
|
class Hello extends React.Component<Props> { |
||||
|
// 在类组件中给 age 添加默认属性
|
||||
|
static defaultProps: Partial<Props> = { |
||||
|
age: 20 |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { name, age } = this.props |
||||
|
return ( |
||||
|
<div>我是:{name}, 我 { age } 岁</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
const App = () => <div> |
||||
|
<Hello name="Ken"/> |
||||
|
</div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,33 @@ |
|||||
|
import React from "react"; |
||||
|
import ReactDOM from "react-dom"; |
||||
|
|
||||
|
// 组件状态和事件
|
||||
|
|
||||
|
type State = { count: number } |
||||
|
class Counter extends React.Component<{}, State> { |
||||
|
state: State = { |
||||
|
count: 0 |
||||
|
} |
||||
|
|
||||
|
handleCLick = () => { |
||||
|
this.setState({ |
||||
|
count: this.state.count + 1 |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div> |
||||
|
计数器: {this.state.count} |
||||
|
<button onClick={this.handleCLick}>+1</button> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const App = () => <div> |
||||
|
<Counter/> |
||||
|
</div> |
||||
|
|
||||
|
ReactDOM.render(<App />, document.getElementById('root')) |
@ -0,0 +1,23 @@ |
|||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
|
|
||||
|
# dependencies |
||||
|
/node_modules |
||||
|
/.pnp |
||||
|
.pnp.js |
||||
|
|
||||
|
# testing |
||||
|
/coverage |
||||
|
|
||||
|
# production |
||||
|
/build |
||||
|
|
||||
|
# misc |
||||
|
.DS_Store |
||||
|
.env.local |
||||
|
.env.development.local |
||||
|
.env.test.local |
||||
|
.env.production.local |
||||
|
|
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
@ -0,0 +1,46 @@ |
|||||
|
# Getting Started with Create React App |
||||
|
|
||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). |
||||
|
|
||||
|
## Available Scripts |
||||
|
|
||||
|
In the project directory, you can run: |
||||
|
|
||||
|
### `npm start` |
||||
|
|
||||
|
Runs the app in the development mode.\ |
||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. |
||||
|
|
||||
|
The page will reload if you make edits.\ |
||||
|
You will also see any lint errors in the console. |
||||
|
|
||||
|
### `npm test` |
||||
|
|
||||
|
Launches the test runner in the interactive watch mode.\ |
||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. |
||||
|
|
||||
|
### `npm run build` |
||||
|
|
||||
|
Builds the app for production to the `build` folder.\ |
||||
|
It correctly bundles React in production mode and optimizes the build for the best performance. |
||||
|
|
||||
|
The build is minified and the filenames include the hashes.\ |
||||
|
Your app is ready to be deployed! |
||||
|
|
||||
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. |
||||
|
|
||||
|
### `npm run eject` |
||||
|
|
||||
|
**Note: this is a one-way operation. Once you `eject`, you can’t go back!** |
||||
|
|
||||
|
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. |
||||
|
|
||||
|
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. |
||||
|
|
||||
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. |
||||
|
|
||||
|
## Learn More |
||||
|
|
||||
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). |
||||
|
|
||||
|
To learn React, check out the [React documentation](https://reactjs.org/). |
28310
TypeScript/04.任务列表案例/todo_ts/package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,43 @@ |
|||||
|
{ |
||||
|
"name": "todo_ts", |
||||
|
"version": "0.1.0", |
||||
|
"private": true, |
||||
|
"dependencies": { |
||||
|
"@testing-library/jest-dom": "^5.16.5", |
||||
|
"@testing-library/react": "^13.3.0", |
||||
|
"@testing-library/user-event": "^13.5.0", |
||||
|
"@types/jest": "^27.5.2", |
||||
|
"@types/node": "^16.11.47", |
||||
|
"@types/react": "^18.0.15", |
||||
|
"@types/react-dom": "^18.0.6", |
||||
|
"react": "^18.2.0", |
||||
|
"react-dom": "^18.2.0", |
||||
|
"react-scripts": "5.0.1", |
||||
|
"typescript": "^4.7.4", |
||||
|
"web-vitals": "^2.1.4" |
||||
|
}, |
||||
|
"scripts": { |
||||
|
"start": "react-scripts start", |
||||
|
"build": "react-scripts build", |
||||
|
"test": "react-scripts test", |
||||
|
"eject": "react-scripts eject" |
||||
|
}, |
||||
|
"eslintConfig": { |
||||
|
"extends": [ |
||||
|
"react-app", |
||||
|
"react-app/jest" |
||||
|
] |
||||
|
}, |
||||
|
"browserslist": { |
||||
|
"production": [ |
||||
|
">0.2%", |
||||
|
"not dead", |
||||
|
"not op_mini all" |
||||
|
], |
||||
|
"development": [ |
||||
|
"last 1 chrome version", |
||||
|
"last 1 firefox version", |
||||
|
"last 1 safari version" |
||||
|
] |
||||
|
} |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="utf-8" /> |
||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||
|
<meta name="theme-color" content="#000000" /> |
||||
|
<meta |
||||
|
name="description" |
||||
|
content="Web site created using create-react-app" |
||||
|
/> |
||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> |
||||
|
<!-- |
||||
|
manifest.json provides metadata used when your web app is installed on a |
||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ |
||||
|
--> |
||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> |
||||
|
<!-- |
||||
|
Notice the use of %PUBLIC_URL% in the tags above. |
||||
|
It will be replaced with the URL of the `public` folder during the build. |
||||
|
Only files inside the `public` folder can be referenced from the HTML. |
||||
|
|
||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will |
||||
|
work correctly both with client-side routing and a non-root public URL. |
||||
|
Learn how to configure a non-root public URL by running `npm run build`. |
||||
|
--> |
||||
|
<title>React App</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<noscript>You need to enable JavaScript to run this app.</noscript> |
||||
|
<div id="root"></div> |
||||
|
<!-- |
||||
|
This HTML file is a template. |
||||
|
If you open it directly in the browser, you will see an empty page. |
||||
|
|
||||
|
You can add webfonts, meta tags, or analytics to this file. |
||||
|
The build step will place the bundled scripts into the <body> tag. |
||||
|
|
||||
|
To begin the development, run `npm start` or `yarn start`. |
||||
|
To create a production bundle, use `npm run build` or `yarn build`. |
||||
|
--> |
||||
|
</body> |
||||
|
</html> |
After Width: 192 | Height: 192 | Size: 5.2 KiB |
After Width: 512 | Height: 512 | Size: 9.4 KiB |
@ -0,0 +1,25 @@ |
|||||
|
{ |
||||
|
"short_name": "React App", |
||||
|
"name": "Create React App Sample", |
||||
|
"icons": [ |
||||
|
{ |
||||
|
"src": "favicon.ico", |
||||
|
"sizes": "64x64 32x32 24x24 16x16", |
||||
|
"type": "image/x-icon" |
||||
|
}, |
||||
|
{ |
||||
|
"src": "logo192.png", |
||||
|
"type": "image/png", |
||||
|
"sizes": "192x192" |
||||
|
}, |
||||
|
{ |
||||
|
"src": "logo512.png", |
||||
|
"type": "image/png", |
||||
|
"sizes": "512x512" |
||||
|
} |
||||
|
], |
||||
|
"start_url": ".", |
||||
|
"display": "standalone", |
||||
|
"theme_color": "#000000", |
||||
|
"background_color": "#ffffff" |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
# https://www.robotstxt.org/robotstxt.html |
||||
|
User-agent: * |
||||
|
Disallow: |
@ -0,0 +1,2 @@ |
|||||
|
@import url('./css/todos-base.css'); |
||||
|
@import url('./css/todos-index.css'); |
@ -0,0 +1,69 @@ |
|||||
|
import { Component } from 'react'; |
||||
|
import { text } from 'stream/consumers'; |
||||
|
import './App.css'; |
||||
|
|
||||
|
import TodoAdd from './components/TodoAdd' |
||||
|
import TodoFooter from './components/TodoFooter' |
||||
|
import TodoList from './components/TodoList' |
||||
|
|
||||
|
// 导入 TodoItem 类型
|
||||
|
import { TodoItem } from './todos' |
||||
|
|
||||
|
// App 组件的状态类型
|
||||
|
type Todos = { |
||||
|
todos: TodoItem[] |
||||
|
} |
||||
|
|
||||
|
const todos: TodoItem[] = [ |
||||
|
{ |
||||
|
id: 1, |
||||
|
text: '吃饭', |
||||
|
done: true |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
text: '休息', |
||||
|
done: false |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
class App extends Component<{}, Todos> { |
||||
|
state: Todos = { |
||||
|
todos |
||||
|
} |
||||
|
|
||||
|
addTodo = (text: string) => { |
||||
|
// console.log('父组件中获取的数据:', text)
|
||||
|
const { todos } = this.state |
||||
|
const id = todos.length === 0 ? 1 :todos[ todos.length - 1 ].id + 1 |
||||
|
this.setState({ |
||||
|
todos: [ ...todos, { |
||||
|
id, |
||||
|
text, |
||||
|
done:false |
||||
|
}] |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<section className='todoapp'> |
||||
|
{/* 添加任务 */} |
||||
|
<TodoAdd onAddTodo={this.addTodo}/> |
||||
|
|
||||
|
<section className='main'> |
||||
|
<input id="toggle-all" className="toggle-all" type="checkbox" /> |
||||
|
<label htmlFor="toggle-all">Mark all as complete</label> |
||||
|
|
||||
|
{/* 列表组件 */} |
||||
|
<TodoList list={this.state.todos} /> |
||||
|
</section> |
||||
|
|
||||
|
{/* footer 组件 */} |
||||
|
<TodoFooter/> |
||||
|
</section> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default App; |
@ -0,0 +1,63 @@ |
|||||
|
import React from "react"; |
||||
|
|
||||
|
// 属性的类型
|
||||
|
type Props = { |
||||
|
onAddTodo(text: string): void |
||||
|
} |
||||
|
|
||||
|
// 状态的类型
|
||||
|
type State = { |
||||
|
text: string |
||||
|
} |
||||
|
|
||||
|
class TodoAdd extends React.Component<Props, State> { |
||||
|
state: State = { |
||||
|
text: '' |
||||
|
} |
||||
|
|
||||
|
onChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
||||
|
this.setState({ |
||||
|
text: e.target.value |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onAdd = (e: React.KeyboardEvent<HTMLInputElement>) => { |
||||
|
// 非空判断
|
||||
|
const { text } = this.state |
||||
|
if (text.trim() === '') return |
||||
|
|
||||
|
// console.log(e.keyCode)
|
||||
|
// 注意:keyCode 属性将来会被弃用,因此,在 TS 中使用的时候,会有一个中横线
|
||||
|
if (e.keyCode === 13) { |
||||
|
this.props.onAddTodo(this.state.text) |
||||
|
// 清空文本框的值
|
||||
|
this.setState({ |
||||
|
text: '' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// 也可以使用 code 属性来代替
|
||||
|
// console.log(e.code)
|
||||
|
// if(e.code === 'Enter') {
|
||||
|
// console.log('enter')
|
||||
|
// }
|
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<header className="header"> |
||||
|
<h1>todos</h1> |
||||
|
<input |
||||
|
className="new-todo" |
||||
|
placeholder="请输入内容" |
||||
|
autoFocus |
||||
|
value={this.state.text} |
||||
|
onChange={this.onChange} |
||||
|
onKeyDown={this.onAdd} |
||||
|
/> |
||||
|
</header> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default TodoAdd |
@ -0,0 +1,29 @@ |
|||||
|
import React from 'react' |
||||
|
|
||||
|
class TodoFooter extends React.Component { |
||||
|
render() { |
||||
|
return ( |
||||
|
<footer className="footer"> |
||||
|
<span className="todo-count"> |
||||
|
<strong>0</strong> item left |
||||
|
</span> |
||||
|
<ul className="filters"> |
||||
|
<li> |
||||
|
<a className="selected" href="#/"> |
||||
|
All |
||||
|
</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#/active">Active</a> |
||||
|
</li> |
||||
|
<li> |
||||
|
<a href="#/completed">Completed</a> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<button className="clear-completed">Clear completed</button> |
||||
|
</footer> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default TodoFooter |
@ -0,0 +1,35 @@ |
|||||
|
import React from "react"; |
||||
|
|
||||
|
// 导入 TodoItem 类型
|
||||
|
import { TodoItem } from '../todos' |
||||
|
|
||||
|
// 提供 props 类型
|
||||
|
interface Props { |
||||
|
list: TodoItem[] |
||||
|
} |
||||
|
|
||||
|
class TodoList extends React.Component<Props> { |
||||
|
|
||||
|
|
||||
|
|
||||
|
render() { |
||||
|
console.log(this.props) |
||||
|
return ( |
||||
|
<ul className="todo-list"> |
||||
|
{ this.props.list.map(todo => { |
||||
|
// 编辑样式:editing 已完成样式:completed
|
||||
|
return <li key={todo.id} className={ todo.done ? 'completed' : '' }> |
||||
|
<div className="view"> |
||||
|
<input className="toggle" type="checkbox" /> |
||||
|
<label>{todo.text}</label> |
||||
|
<button className="destroy"></button> |
||||
|
</div> |
||||
|
<input className="edit" defaultValue="Create a TodoMVC template" /> |
||||
|
</li> |
||||
|
})} |
||||
|
</ul> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default TodoList |
@ -0,0 +1,141 @@ |
|||||
|
hr { |
||||
|
margin: 20px 0; |
||||
|
border: 0; |
||||
|
border-top: 1px dashed #c5c5c5; |
||||
|
border-bottom: 1px dashed #f7f7f7; |
||||
|
} |
||||
|
|
||||
|
.learn a { |
||||
|
font-weight: normal; |
||||
|
text-decoration: none; |
||||
|
color: #b83f45; |
||||
|
} |
||||
|
|
||||
|
.learn a:hover { |
||||
|
text-decoration: underline; |
||||
|
color: #787e7e; |
||||
|
} |
||||
|
|
||||
|
.learn h3, |
||||
|
.learn h4, |
||||
|
.learn h5 { |
||||
|
margin: 10px 0; |
||||
|
font-weight: 500; |
||||
|
line-height: 1.2; |
||||
|
color: #000; |
||||
|
} |
||||
|
|
||||
|
.learn h3 { |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
|
||||
|
.learn h4 { |
||||
|
font-size: 18px; |
||||
|
} |
||||
|
|
||||
|
.learn h5 { |
||||
|
margin-bottom: 0; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.learn ul { |
||||
|
padding: 0; |
||||
|
margin: 0 0 30px 25px; |
||||
|
} |
||||
|
|
||||
|
.learn li { |
||||
|
line-height: 20px; |
||||
|
} |
||||
|
|
||||
|
.learn p { |
||||
|
font-size: 15px; |
||||
|
font-weight: 300; |
||||
|
line-height: 1.3; |
||||
|
margin-top: 0; |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
#issue-count { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.quote { |
||||
|
border: none; |
||||
|
margin: 20px 0 60px 0; |
||||
|
} |
||||
|
|
||||
|
.quote p { |
||||
|
font-style: italic; |
||||
|
} |
||||
|
|
||||
|
.quote p:before { |
||||
|
content: '“'; |
||||
|
font-size: 50px; |
||||
|
opacity: .15; |
||||
|
position: absolute; |
||||
|
top: -20px; |
||||
|
left: 3px; |
||||
|
} |
||||
|
|
||||
|
.quote p:after { |
||||
|
content: '”'; |
||||
|
font-size: 50px; |
||||
|
opacity: .15; |
||||
|
position: absolute; |
||||
|
bottom: -42px; |
||||
|
right: 3px; |
||||
|
} |
||||
|
|
||||
|
.quote footer { |
||||
|
position: absolute; |
||||
|
bottom: -40px; |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.quote footer img { |
||||
|
border-radius: 3px; |
||||
|
} |
||||
|
|
||||
|
.quote footer a { |
||||
|
margin-left: 5px; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
.speech-bubble { |
||||
|
position: relative; |
||||
|
padding: 10px; |
||||
|
background: rgba(0, 0, 0, .04); |
||||
|
border-radius: 5px; |
||||
|
} |
||||
|
|
||||
|
.speech-bubble:after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 100%; |
||||
|
right: 30px; |
||||
|
border: 13px solid transparent; |
||||
|
border-top-color: rgba(0, 0, 0, .04); |
||||
|
} |
||||
|
|
||||
|
.learn-bar > .learn { |
||||
|
position: absolute; |
||||
|
width: 272px; |
||||
|
top: 8px; |
||||
|
left: -300px; |
||||
|
padding: 10px; |
||||
|
border-radius: 5px; |
||||
|
background-color: rgba(255, 255, 255, .6); |
||||
|
transition-property: left; |
||||
|
transition-duration: 500ms; |
||||
|
} |
||||
|
|
||||
|
@media (min-width: 899px) { |
||||
|
.learn-bar { |
||||
|
width: auto; |
||||
|
padding-left: 300px; |
||||
|
} |
||||
|
|
||||
|
.learn-bar > .learn { |
||||
|
left: 8px; |
||||
|
} |
||||
|
} |
@ -0,0 +1,379 @@ |
|||||
|
html, |
||||
|
body { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
button { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
border: 0; |
||||
|
background: none; |
||||
|
font-size: 100%; |
||||
|
vertical-align: baseline; |
||||
|
font-family: inherit; |
||||
|
font-weight: inherit; |
||||
|
color: inherit; |
||||
|
-webkit-appearance: none; |
||||
|
appearance: none; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; |
||||
|
line-height: 1.4em; |
||||
|
background: #f5f5f5; |
||||
|
color: #4d4d4d; |
||||
|
min-width: 230px; |
||||
|
max-width: 550px; |
||||
|
margin: 0 auto; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
font-weight: 300; |
||||
|
} |
||||
|
|
||||
|
:focus { |
||||
|
outline: 0; |
||||
|
} |
||||
|
|
||||
|
.hidden { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.todoapp { |
||||
|
background: #fff; |
||||
|
margin: 130px 0 40px 0; |
||||
|
position: relative; |
||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), |
||||
|
0 25px 50px 0 rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.todoapp input::-webkit-input-placeholder { |
||||
|
font-style: italic; |
||||
|
font-weight: 300; |
||||
|
color: #e6e6e6; |
||||
|
} |
||||
|
|
||||
|
.todoapp input::-moz-placeholder { |
||||
|
font-style: italic; |
||||
|
font-weight: 300; |
||||
|
color: #e6e6e6; |
||||
|
} |
||||
|
|
||||
|
.todoapp input::input-placeholder { |
||||
|
font-style: italic; |
||||
|
font-weight: 300; |
||||
|
color: #e6e6e6; |
||||
|
} |
||||
|
|
||||
|
.todoapp h1 { |
||||
|
position: absolute; |
||||
|
top: -155px; |
||||
|
width: 100%; |
||||
|
font-size: 100px; |
||||
|
font-weight: 100; |
||||
|
text-align: center; |
||||
|
color: rgba(175, 47, 47, 0.15); |
||||
|
-webkit-text-rendering: optimizeLegibility; |
||||
|
-moz-text-rendering: optimizeLegibility; |
||||
|
text-rendering: optimizeLegibility; |
||||
|
} |
||||
|
|
||||
|
.new-todo, |
||||
|
.edit { |
||||
|
position: relative; |
||||
|
margin: 0; |
||||
|
width: 100%; |
||||
|
font-size: 24px; |
||||
|
font-family: inherit; |
||||
|
font-weight: inherit; |
||||
|
line-height: 1.4em; |
||||
|
border: 0; |
||||
|
color: inherit; |
||||
|
padding: 6px; |
||||
|
border: 1px solid #999; |
||||
|
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); |
||||
|
box-sizing: border-box; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
} |
||||
|
|
||||
|
.new-todo { |
||||
|
padding: 16px 16px 16px 60px; |
||||
|
border: none; |
||||
|
background: rgba(0, 0, 0, 0.003); |
||||
|
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); |
||||
|
} |
||||
|
|
||||
|
.main { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
border-top: 1px solid #e6e6e6; |
||||
|
} |
||||
|
|
||||
|
.toggle-all { |
||||
|
width: 1px; |
||||
|
height: 1px; |
||||
|
border: none; /* Mobile Safari */ |
||||
|
opacity: 0; |
||||
|
position: absolute; |
||||
|
right: 100%; |
||||
|
bottom: 100%; |
||||
|
} |
||||
|
|
||||
|
.toggle-all + label { |
||||
|
width: 60px; |
||||
|
height: 34px; |
||||
|
font-size: 0; |
||||
|
position: absolute; |
||||
|
top: -52px; |
||||
|
left: -13px; |
||||
|
-webkit-transform: rotate(90deg); |
||||
|
transform: rotate(90deg); |
||||
|
} |
||||
|
|
||||
|
.toggle-all + label:before { |
||||
|
content: '❯'; |
||||
|
font-size: 22px; |
||||
|
color: #e6e6e6; |
||||
|
padding: 10px 27px 10px 27px; |
||||
|
} |
||||
|
|
||||
|
.toggle-all:checked + label:before { |
||||
|
color: #737373; |
||||
|
} |
||||
|
|
||||
|
.todo-list { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
list-style: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li { |
||||
|
position: relative; |
||||
|
font-size: 24px; |
||||
|
border-bottom: 1px solid #ededed; |
||||
|
} |
||||
|
|
||||
|
.todo-list li:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li.editing { |
||||
|
border-bottom: none; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.todo-list li.editing .edit { |
||||
|
display: block; |
||||
|
width: 506px; |
||||
|
padding: 12px 16px; |
||||
|
margin: 0 0 0 43px; |
||||
|
} |
||||
|
|
||||
|
.todo-list li.editing .view { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .toggle { |
||||
|
text-align: center; |
||||
|
width: 40px; |
||||
|
/* auto, since non-WebKit browsers doesn't support input styling */ |
||||
|
height: auto; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
margin: auto 0; |
||||
|
border: none; /* Mobile Safari */ |
||||
|
-webkit-appearance: none; |
||||
|
appearance: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .toggle { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .toggle + label { |
||||
|
/* |
||||
|
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 |
||||
|
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ |
||||
|
*/ |
||||
|
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: center left; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .toggle:checked + label { |
||||
|
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E'); |
||||
|
} |
||||
|
|
||||
|
.todo-list li label { |
||||
|
word-break: break-all; |
||||
|
padding: 15px 15px 15px 60px; |
||||
|
display: block; |
||||
|
line-height: 1.2; |
||||
|
transition: color 0.4s; |
||||
|
} |
||||
|
|
||||
|
.todo-list li.completed label { |
||||
|
color: #d9d9d9; |
||||
|
text-decoration: line-through; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .destroy { |
||||
|
display: none; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 10px; |
||||
|
bottom: 0; |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
margin: auto 0; |
||||
|
font-size: 30px; |
||||
|
color: #cc9a9a; |
||||
|
margin-bottom: 11px; |
||||
|
transition: color 0.2s ease-out; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .destroy:hover { |
||||
|
color: #af5b5e; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .destroy:after { |
||||
|
content: '×'; |
||||
|
} |
||||
|
|
||||
|
.todo-list li:hover .destroy { |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .edit { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li.editing:last-child { |
||||
|
margin-bottom: -1px; |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
color: #777; |
||||
|
padding: 10px 15px; |
||||
|
height: 20px; |
||||
|
text-align: center; |
||||
|
border-top: 1px solid #e6e6e6; |
||||
|
} |
||||
|
|
||||
|
.footer:before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
height: 50px; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), |
||||
|
0 8px 0 -3px #f6f6f6, |
||||
|
0 9px 1px -3px rgba(0, 0, 0, 0.2), |
||||
|
0 16px 0 -6px #f6f6f6, |
||||
|
0 17px 2px -6px rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.todo-count { |
||||
|
float: left; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.todo-count strong { |
||||
|
font-weight: 300; |
||||
|
} |
||||
|
|
||||
|
.filters { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
list-style: none; |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.filters li { |
||||
|
display: inline; |
||||
|
} |
||||
|
|
||||
|
.filters li a { |
||||
|
color: inherit; |
||||
|
margin: 3px; |
||||
|
padding: 3px 7px; |
||||
|
text-decoration: none; |
||||
|
border: 1px solid transparent; |
||||
|
border-radius: 3px; |
||||
|
} |
||||
|
|
||||
|
.filters li a:hover { |
||||
|
border-color: rgba(175, 47, 47, 0.1); |
||||
|
} |
||||
|
|
||||
|
.filters li a.selected { |
||||
|
border-color: rgba(175, 47, 47, 0.2); |
||||
|
} |
||||
|
|
||||
|
.clear-completed, |
||||
|
html .clear-completed:active { |
||||
|
float: right; |
||||
|
position: relative; |
||||
|
line-height: 20px; |
||||
|
text-decoration: none; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.clear-completed:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.info { |
||||
|
margin: 65px auto 0; |
||||
|
color: #bfbfbf; |
||||
|
font-size: 10px; |
||||
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.info p { |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.info a { |
||||
|
color: inherit; |
||||
|
text-decoration: none; |
||||
|
font-weight: 400; |
||||
|
} |
||||
|
|
||||
|
.info a:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
Hack to remove background from Mobile Safari. |
||||
|
Can't use it globally since it destroys checkboxes in Firefox |
||||
|
*/ |
||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) { |
||||
|
.toggle-all, |
||||
|
.todo-list li .toggle { |
||||
|
background: none; |
||||
|
} |
||||
|
|
||||
|
.todo-list li .toggle { |
||||
|
height: 40px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 430px) { |
||||
|
.footer { |
||||
|
height: 50px; |
||||
|
} |
||||
|
|
||||
|
.filters { |
||||
|
bottom: 10px; |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
import ReactDOM from 'react-dom/client'; |
||||
|
import App from './App'; |
||||
|
|
||||
|
const root = ReactDOM.createRoot( |
||||
|
document.getElementById('root') as HTMLElement |
||||
|
); |
||||
|
root.render( |
||||
|
<App /> |
||||
|
); |
||||
|
|
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg> |
@ -0,0 +1 @@ |
|||||
|
/// <reference types="react-scripts" />
|
@ -0,0 +1,15 @@ |
|||||
|
import { ReportHandler } from 'web-vitals'; |
||||
|
|
||||
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => { |
||||
|
if (onPerfEntry && onPerfEntry instanceof Function) { |
||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { |
||||
|
getCLS(onPerfEntry); |
||||
|
getFID(onPerfEntry); |
||||
|
getFCP(onPerfEntry); |
||||
|
getLCP(onPerfEntry); |
||||
|
getTTFB(onPerfEntry); |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default reportWebVitals; |
@ -0,0 +1,5 @@ |
|||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
|
// allows you to do things like:
|
||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||
|
import '@testing-library/jest-dom'; |
@ -0,0 +1,8 @@ |
|||||
|
// 类型声明文件
|
||||
|
|
||||
|
// 任务项的类型
|
||||
|
export type TodoItem = { |
||||
|
id: number |
||||
|
text: string |
||||
|
done: boolean |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "es5", |
||||
|
"lib": [ |
||||
|
"dom", |
||||
|
"dom.iterable", |
||||
|
"esnext" |
||||
|
], |
||||
|
"allowJs": true, |
||||
|
"skipLibCheck": true, |
||||
|
"esModuleInterop": true, |
||||
|
"allowSyntheticDefaultImports": true, |
||||
|
"strict": true, |
||||
|
"forceConsistentCasingInFileNames": true, |
||||
|
"noFallthroughCasesInSwitch": true, |
||||
|
"module": "esnext", |
||||
|
"moduleResolution": "node", |
||||
|
"resolveJsonModule": true, |
||||
|
"isolatedModules": true, |
||||
|
"noEmit": true, |
||||
|
"jsx": "react-jsx" |
||||
|
}, |
||||
|
"include": [ |
||||
|
"src" |
||||
|
] |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue