1 环境
$ npx create-react-app test --typescript
2 function组件
props
一般类型
interface IProps{
param_1: string
param_2: number
param_3: boolean
param_4: string[]
param_5?: number[]
......
}
function App(props:IProps) {
return null
}
类型为function组件
import React from 'react';
interface ITest { name: string }
function Test({ name }: ITest) {
return <span>{name}</span>
}
function App({ TestComponent }: { TestComponent: React.FC<ITest> }) {
return <TestComponent name={'xxx'} />
}
export default function () {
return <App
TestComponent={Test}
/>
}
类型为class组件
import React from 'react';
interface IProps { age: number }
interface IState { name: string }
class Test extends React.Component<IProps, IState> {
state = { name: '林' }
render() {
return <span>{this.props.age}</span>
}
}
function App({ TestComponent }: { TestComponent: React.ComponentType<IProps> }) {
return <TestComponent age={18} />
}
export default function () {
return <App
TestComponent={Test}
/>
}
useReducer
import React, { useReducer } from 'react';
type Action = {
type: 'setOpen';
data: {open: boolean}
} | {
type: 'setName';
data: {name: string}
}
interface IInit {
open: boolean
name: string
}
const init: IInit = {
open: true,
name: '1'
}
function myReducer(data: IInit, action: Action): IInit {
switch (action.type) {
case 'setOpen':
return {...data,...action.data}
case 'setName':
return {...data,...action.data}
default:
return data;
}
}
function App() {
const [state, dispatch] = useReducer(myReducer, init);
return (
<>
<div>{state.open ? 'open为true' : 'open为false'}</div>
<button
onClick={() => {
if (state.open) {
dispatch({ type: 'setOpen', data: { open: false } })
} else {
dispatch({ type: 'setOpen', data: { open: true } })
}
}}
>
切换open
</button>
<button
onClick={() => dispatch({ type: 'setName', data: { name: '2' } })}
>
切换flag
</button>
</>
);
}
export default App;
useRef
import React, { useRef, MutableRefObject } from 'react';
import Child from './Child';
function Father() {
const h1Ref = useRef() as MutableRefObject<HTMLHeadingElement>;
const divRef = useRef() as MutableRefObject<HTMLDivElement>;
const ulRef = useRef() as MutableRefObject<HTMLUListElement>;
return (
<>
<h1 ref={h1Ref}></h1>
<div ref={divRef}></div>
<ul ref={ulRef}></ul>
<canvas></canvas>
</>
)
}
export default Father;
useImperativeHandle
import React, { useImperativeHandle } from 'react';
export interface IChildHandler {
speak: () => void
}
interface IChildProps { name?: string }
const Child = React.forwardRef<IChildHandler, IChildProps>(({ name }, ref) => {
useImperativeHandle(ref, () => ({
speak() {
console.log('访问到组件里的内容了')
}
}))
return null;
});
export default Child;
useRef | useImperativeHandle
import React, { useRef, MutableRefObject, useCallback } from 'react';
import Child, { IChildHandler } from './Child';
function Father() {
const childRef = useRef() as MutableRefObject<IChildHandler>;
return <>
<Child ref={childRef} />
<button onClick={
useCallback(() => {
childRef.current.speak();
}, [])
}>
触发child中的speak
</button>
</>
}
export default Father;
useContext
- context.ts
import { createContext } from 'react';
export interface IContext {
name: string
obj: {
age: number
}
}
export default createContext<Partial<IContext>>({});
- Father.tsx
import MyContext from './context';
function Father() {
return (
<MyContext.Provider value={{ name: '林', obj: { age: 18 } }}>
<Child />
</MyContext.Provider >
)
}
- Child.tsx
import MyContext, { IContext } from './context';
function Child() {
const { name, obj } = useContext(MyContext) as IContext;
return <span>{name}-{obj.age}</span>
}
3 redux
官方例子 https://redux.js.org/recipes/usage-with-typescript
$ yarn add react-redux redux @types/react-redux
3.0 基础文件
3.0.1 store/login/type.ts
// src/store/login/type.ts
import { SET_LOGIN } from './';
export type LoginState = boolean; // 定义登陆状态的数据类型
export interface UpdateLogin { // 定义登陆状态的action
type: typeof SET_LOGIN
payload: LoginState
}
3.0.2 store/login/index.ts
// src/store/login/index.ts
import { LoginState, UpdateLogin } from './type';
// Key
export const SET_LOGIN = 'SET_LOGIN';
// initalValue
const initialState: LoginState = false; // 是否已经登陆
// Action
export function updateLoginState(newLoginState: LoginState): UpdateLogin {
return {
type: SET_LOGIN,
payload: newLoginState
}
}
// Reducer
export default function (state = initialState, action: UpdateLogin): LoginState {
switch (action.type) {
case SET_LOGIN: return action.payload;
default: return state;
}
}
3.0.3 store/chat/type.ts
// src/store/chat/type.ts
import { SEND_MESSAGE, DELETE_MESSAGE } from './';
export interface Message {
user: string
message: string
timestamp: number
}
export interface ChatState {
messages: Message[]
}
interface SendMessageAction {
type: typeof SEND_MESSAGE
newMessage: Message
}
interface DeleteMessageAction {
type: typeof DELETE_MESSAGE
timestamp: number
}
export type ChatActionTypes = SendMessageAction | DeleteMessageAction
3.0.4 store/chat/index.ts
// src/store/chat/index.ts
import { ChatActionTypes, ChatState, Message } from './type';
// Key
export const SEND_MESSAGE = 'SEND_MESSAGE'
export const DELETE_MESSAGE = 'DELETE_MESSAGE'
// initalValue
const initialState: ChatState = { messages: [] }
// Action
export function sendMessage(newMessage: Message): ChatActionTypes {
return { type: SEND_MESSAGE, newMessage }
}
export function deleteMessage(timestamp: number): ChatActionTypes {
return { type: DELETE_MESSAGE, timestamp }
}
// Reducer
export default function (
state = initialState,
action: ChatActionTypes
): ChatState {
switch (action.type) {
case SEND_MESSAGE:
return { messages: [...state.messages, action.newMessage] }
case DELETE_MESSAGE:
return {
messages: state.messages.filter(
message => message.timestamp !== action.timestamp
)
}
default:
return state
}
}
3.0.5 store/index.ts
// src/store/index.ts
import { combineReducers, createStore } from 'redux';
import loginRefucer from './login'
import chatReducer from './chat'
const rootReducer = combineReducers({
login: loginRefucer,
chat: chatReducer
})
export type RootState = ReturnType<typeof rootReducer>
export default createStore(rootReducer);
3.0.6 index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store/index';
import Child from './child'
function Father() {
return (
<Provider store={store}>
<Child />
</Provider>
)
}
ReactDOM.render(<Father />, document.getElementById('root'));
3.1 hooks
3.1.1 useSelector
import React from 'react';
import { Provider, useSelector, useStore, useDispatch } from 'react-redux';
import store, { RootState } from './store/index'
function Child() {
const loginState = useSelector((state: RootState) => state.login);
return <span>现在的登陆状态为:{loginState ? '已登陆' : '未登陆'}</span>
}
3.1.2 useDispatch
- store/index.ts
export type AppDispatch = typeof store.dispatch;
- index.tsx
....
import store, { RootState, AppDispatch } from './store/index'
function Child(){
const dispatch: AppDispatch = useDispatch();
useEffect(()=>{
dispatch({ type: 'SET_LOGIN', payload: true });
},[])
return null
}
4 ts中使用js
4.1 导入js组件
如果在ts项目中导入js写的库,因为没有类型的描述文件就会报错
这是因为这个库过于老旧或者官方没有为其编写类型文件,解决这个方法之一,就是在 tsconfig.josn 中添加下面一行配置
{
......
"noImplicitAny": false,
......
}
现在再编译就不会报错了,虽然不会报错,但是引入的库后面会显示这几个小点点,表示ts的编译器不知道其类型,相当于any
4.2 安装别人写好的声明文件
先把 noImplicitAny
改为true
, 还记得上面的提示吗,webpack
让 我们 安装 @types/react-fastclick
,这是因为 ts 为了让一些用js写成的库在ts环境下能正常访问,所以如果 这个库的声明文件在 源码目录下找不到,会去 node_modules
的 @types
目录下查找相关的声明文件
这个目录下一般是官方不想使用 ts 重写一遍代码,所以直接的写一遍类型文件
或者为这个库的爱好者为其官方书写的类型文件
4.2 自定义d.ts
但是我们通过安装发现,并没有 @types/react-fastclick
这个库,说明没有人为其书写类型声明文件,那我们可以自己的为其书写声明文件
- 项目/tsconfig.json
{
"compilerOptions": {
......
"baseUrl": ".",
"paths": {
"*":[
"./node_modules/@tpyes", // 声明文件先去node_modules/@tpyes下找
"./typings/*" // 如果node_modules下找不到,就去这个文件夹下找
]
}
},
}
- 项目/typings/react-fastclick/index.d.ts
declare function fastClick(): void;
export default fastClick;
编写完成之后,就会发现 vscode 不会有三个小点了