react + ts 下的开发经验汇总
学习过java语言,才发现类型标注的重要性,在前端,js最让人诟病的便是无类型规范,可以随意赋值和改变类型等,这样项目出现bug的几率大大增加,而js的超集typescript完美解决了这个问题。(别犹豫是否该学ts了,说白了,ts才是前端的脸面,何况主流框架和库早就推出了ts环境下的开发方式和类型标注)
1. 声明函数组件或类组件
函数组件的类型 React.FC 或 FC 接受一个泛型作为父级传递过来的值的约束!
import Reeact ,{FC} from "react";
interface ExtendedProps{title:string,value:number
}
const WrapComponent: React.FC<ExtendedProps> = (props) => {return (<div> {title} </div> )
};orconst WrapComponent: FC<ExtendedProps> = (props) => {return (<div> {value} </div> )
};export default WrapComponent;
函数组件接受父级“插槽”内容时children的类型声明:
1.类型断言
const App: React.FC<{}> = props => (props.children as unknown) as JSX.Elementconst App: React.FC<{}> = () => ([1, 2, 3] as unknown) as JSX.Elementconst App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element
2.使用 PropsWithChildren
type AppProps = React.PropsWithChildren<{ message: string }>const App = ({ message, children }: AppProps) => (<div>{message}{children}</div>
)
3.直接声明:
//子组件
import React from 'react'
type AppProps = {message: stringchildren?: React.ReactNode
}const App :React.FC = ({ message, children }: AppProps) => (<div>{message}{children}</div>
)//父组件cosnt Parent:React.FC=()=>{return (<App message="阿尼亚我的宝~"> <span>上坂堇我的宝!</span> <App />)
}
类组件在继承React.Compinent时需要传递两个泛型变量
分别是类中的state和父级传递props的类型约束
type IEProps {Cp?: React.ComponentClass<{ id?: number }>;
}
type IEState { id: number; }class ClassCpWithModifier extends React.Component<IEProps, IEState> {private gid: number = 1;public state: Readonly<IEState> = { id: 1 };state = { //无需构造器,状态直接定义在类中即可transferData: {optimizationOrderFn: false,allocationFn: false,releaseFn: false,revocationFn: false,},};render() { const { transferData } = this.state || {};return (<span> {id} </span><div> {transferData} </div>)}
}
2.事件
- onClick and onChange
export default let eventComp:FC<Iprops> = (props)=>{ const onClick = (e: React.MouseEvent<HTMLInputElement>) => {console.log("点击事件"); };const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {console.log("值改变了~") };return (<><ProForm<DataType> /><input onClick={onClick} onChange={onChange} /></>);}
- Forms and onSubmit
import * as React from 'react'type changeFn = (e: React.FormEvent<HTMLInputElement>) => voidconst App: React.FC = () => {const [state, setState] = React.useState('')const onChange: changeFn = e => {setState(e.currentTarget.value)}✅ betterconst onSubmit = (e: React.SyntheticEvent) => {e.preventDefault()const target = e.target as typeof e.target & {password: { value: string }} // 类型扩展const password = target.password.value}return (<form onSubmit={onSubmit}><input type="text" value={state} onChange={onChange} /></form>) }
3.react-hooks
const [val, toggle] = useState<boolean>(false);const [val, toggle] = useState<number>(0);interface msg{sex:string,age:number,name:string}
interface props{num:number,users: msg[]}const [val, changeVal] = useState<props>(
{... props });
4.对象类型的声明
type ObjectTypes = {objBetter: Record<string, unknown>; // ✅ better,代替 obj: object// 对于 obj2: {}; 有三种情况:obj2Better1: Record<string, unknown>; // ✅ better 同上obj2Better2: unknown; // ✅ any valueobj2Better3: Record<string, never>; // ✅ 空对象/** Record 更多用法 */dict1: {[key: string]: MyTypeHere;};dict2: Record<string, MyTypeHere>; // 等价于 dict1
};//好处://1.当你书写 home 值时,键入 h 常用的编辑器有智能补全提示;
//2.home 拼写错误成 hoem,会有错误提示,往往这类错误很隐蔽;
//3.收窄接收的边界。
5.参数传递
组件之间传递参数需要用接口来约束类型,父级传递参数严格遵守接口定义的规则
interface trans {optimizationOrderFn: boolean,allocationFn: boolean,releaseFn: boolean,revocationFn: boolean
}interface Iprops {val:trans
}
默认参数和可选参数
type GreetProps = { age?: number }const Greet = ({ age = 21 }: GreetProps) => {/* ... */}
子组件无法决定传递参数的类型时:
type Props<T> = {name: Tname2?: T
}const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => {return (<div className="test-b">TestB--{name}{name2}</div>)
}const TestD = () => {return (<div><TestC <string> name="123" /></div>)
}
6.自定义hooks的返回值类型
需要注意,自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,而我们实际需要的是数组里里每一项的具体类型,
需要手动添加 const 断言 进行处理:
function useLoading() {const [isLoading, setState] = React.useState(false)const load = (aPromise: Promise<any>) => {setState(true)return aPromise.then(() => setState(false))}// 实际需要: [boolean, typeof load] 类型// 而不是自动推导的:(boolean | typeof load)[]return [isLoading, load] as const}
或者直接声明返回值类型
export function useLoading(): [boolean,(aPromise: Promise<any>) => Promise<any>] {const [isLoading, setState] = React.useState(false)const load = (aPromise: Promise<any>) => {setState(true)return aPromise.then(() => setState(false))}return [isLoading, load]}
3.封装一个工具函数
export default function tuplify<T extends any[]>(...elements: T) {return elements
}function useTupleLoading() {const [isLoading, setState] = React.useState(false)const load = (aPromise: Promise<any>) => {setState(true)return aPromise.then(() => setState(false))}// [boolean, typeof load]return tuplify(isLoading, load)}
7.ts环境下类组件路由操作
官方已经想让你抛弃类组件了,react-Router新版本只支持 hooks,而在类组件中使用路由相关操作,需要利用hooks来封装
封装:
import React from "react";
import { NavigateFunction, useLocation, useNavigate, useParams } from "react-router";export interface RoutedProps<Params = any, State = any> {location: State;navigate: NavigateFunction;params: Params;
}export function withRouter<P extends RoutedProps>( Child: React.ComponentClass<P> ) {return ( props: Omit<P, keyof RoutedProps> ) => {const location = useLocation();const navigate = useNavigate();const params = useParams();return <Child { ...props as P } navigate={ navigate } location={ location } params={ params }/>;}
}
调用:
import React from "react";
import FrameLoader from "./game";
import { NavBar, Space, Toast } from 'antd-mobile';
import { withRouter, RoutedProps } from '../../../hooks/withRouter';
interface trans {optimizationOrderFn: boolean,allocationFn: boolean,releaseFn: boolean,revocationFn: boolean
}
interface Istate {transferData: trans
}
interface Iprops {val: any
}
class FactoryInfo extends React.Component<RoutedProps, Istate> {state = {transferData: {optimizationOrderFn: false,allocationFn: false,releaseFn: false,revocationFn: false,},};componentDidMount() {console.log(this.props);// 接收Iframe 传递的数据window.addEventListener("message", (e) => {const { allocationFn, releaseFn } = e.data || {}; //传递的数据if (allocationFn || releaseFn) {this.setState({transferData: e.data,});}});}back = () => {Toast.show({content: '马上就给你返回,别着急!',duration: 2000,})//console.log(this.props.location.state.url);this.props.navigate("/home");}render() {const { transferData } = this.state || {};const url = this.props.location.state.url;console.log(transferData, "数据");return (<div style={{ height: "100%" }}><NavBar onBack={this.back}>标题</NavBar><div style={{ minHeight: "90vh", position: "relative" }}><FrameLoader height={1000} url={"./game/" + url + ".html"} /></div></div>);}
}
export default withRouter(FactoryInfo)
生命周期调用两次请求的解决方案
将 componentDidMount() 改写为 componentWillMount()
hooks中的解决
let getNews = () => {axios({method: 'get',url: "https://api.apiopen.top/api/getImages?type=scenery&page=0&size=10"}).then((res) => {console.log(res);changeList(res.data.result.list)})}useEffect(() => {getNews();}, [])//注意方法单独封装,在hooks里调用,直接将请求写入hooks会发送两次...
react中的防抖实现
防抖的句柄timer在组件重新渲染时丢失,通过useCallback缓存该方法即可
const Search = (props: IProps) => {let [val, changeVal] = useState('');let changeInner = () => {let timer: any = null;return function () {if (!timer) {timer = setTimeout(() => {console.log("值改变了!");timer = null;}, 1000);}}}const change= useCallback(changeInner(), []);return (<div className="App"><input type="text" value={val} onChange={(e) => {changeVal(e.target.value),change()}} /></div>);
}
关于redux的一些思考
redux核心文件 createStore 封装了三个方法 ,dispatch,getState,subScribe
接受reducer, preloadedState, enhancer作为createStore实例的参数,
有中间件的情况下createStore需要通过enhancer中的compose函数进行中间件处理,之后执行dispatch,触发reducer从而初始化state
store创建好之后,立即发出一个初始化action,是为了让reducer返回store的初始化状态,否则,创建store之后,调用getState方法得到的就是undefined。
import { createStore } from 'redux'
function counterReducer(state = { value: 0 }, action) {switch (action.type) {case 'counter/incremented':return { value: state.value + 1 }default:return state}
}let store = createStore(counterReducer)
store.dispatch({ type: 'counter/incremented' })
dispatch :根据传递的旧状态和新action 触发reducer来改变state,之后触发所有监听器
subScribe : 返回一个unsubScribe 方法,负责添加订阅事件和取消事件
全局的数据有 : state reducer currentListeners nextListeners isDispatching(全局锁)
派发监听器的时候有可能在监听的方法内存在再次订阅或取消监听的方法,这样会导致 currentListeners 长度改变从而报错,所以需要在派发前浅拷贝一下,订阅和取消操作nextListeners,在派发之前进行拷贝 currentListeners = nextListeners
全局锁的作用:为了防止用户在时事件派发的同时再获取state,在派发时 isDispatching为true,完成后为false
中间件类似于洋葱模型,利用compose函数实现redux实例和中间件的嵌套
combineReducers
reducer函数负责生成state,整个应用只有一个state对象,包含所有的数据,对于一个大型应用来说,state必然比较庞大,会导致reducer函数也十分庞大,所以需要对reducer函数进行拆分。并且几个人开发的项目中,我们需要编写各自的reucer函数并合并在一起,因为createStore只能接收一个reducer函数。这就是combineReducers函数做的事情。
下面来举例说明:
假设我们项目当中有两个reducer函数,reducerI 和 reducerII 函数,将两个reducer函数以对象的形式传入combinereducers函数,函数执行返回的finalReducer作为reducer函数传给createStore,实现了原本reducer函数相同的功能。
combineReducers接收reducers对象为入参,返回一个合成的reducer函数,利用闭包,实现了reducer合成的功能。实质是通过遍历的形式,将action传入每一个reducer函数,获取初始值或者改变state,返回新的state对象
// 参数reducers是一个对象,key值和state的key值一样,value是一个reducer函数
function combineReducers(reducers) {const reducerKeys = Object.keys(reducers)const finalReducers = {}// 遍历key值,过滤掉value不是reducer函数的值for (let i = 0; i < reducerKeys.length; i++) {const key = reducerKeys[i]// 过滤掉value不是reducer函数的值if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key]}}const finalReducerKeys = Object.keys(finalReducers)// 返回一个合成的reducer函数(接收state和action作为参数)return function combination(state = {}, action) {let hasChanged = false // 记录state对象是否改变的变量const nextState = {} // 下一个状态// 遍历key值for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]const previousStateForKey = state[key] // 当前key值对应的状态const nextStateForKey = reducer(previousStateForKey, action) //调用reducer后新生成的状态nextState[key] = nextStateForKey //将新生成的状态赋值到新状态的对应key值上// 通过对比previousStateForKey 和 nextStateForKey是否相等来表明状态是否改变hasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 如果状态改变就返回新的状态,没有,就返回原来的state对象return hasChanged ? nextState : state}
}
// combineReducers函数实际上也是一个reducer函数,接收state和action作为参数,返回一个新的state
使用react实现 通讯录功能
思路:
得到地区数据,改变其数据结构,提取地区首字母作为key,地区为value组成新的数组对象,注意地区按字母进行排序。
let transdata = (arr) => {let map = {};arr.forEach((ele) => {let str = ele.short[0].toUpperCase();if (map[str]) {map[str] = [...map[str], ele.label];} else {map[str] = [ele.label];}});objToArr(map);};let objToArr=(obj)=>{let arr = [];for (const key in obj) {arr.push({key:key,value:obj[key],smallKey:key.toLowerCase()})}arr.sort((a,b)=>{if(a.key<b.key){return -1}else{return 1}return 0;})setCitys(arr);}
再之后就是渲染
这里点击右侧字母实现相应跳转利用了a标签的锚点属性~
citys.map((item,index)=>(<div className="citys" key={index}><div id={item.key}>{item.key}</div><ul>{item.value.map(i=>(<li key={i}>{i}</li>) )}</ul></div>))<ul className="aside">{citys.map(item=>(<a key={item.key} href={"#"+item.key}>{item.smallKey}</a>))}</ul>
react + ts 下的开发经验汇总相关推荐
- React TypeScript react+ts 包下载
react+ts下载使用 要使用 TypeScript 启动新的 Create React App 项目,您可以做以下 搭建TS+React的开发环境 Create React App 是一种官方支持 ...
- 创建React + Ts项目
一.安装react+ts npx create-react-app my-app --template typescript 二.安装eslint代码检测 yarn eslint npx eslint ...
- React+TS学习和使用(三):React Redux和项目的路由配置
开启学习react+ts,本篇主要是学习使用React Redux和项目的路由配置 一.React Redux 需求:使用TS+React Redux实现一个累加. A. 安装 $ yarn add ...
- React + TS项目开发小技巧总结
一.react hook知识 1.基本使用 最常用的Hook,有两个:useState.useEffect import React, { useState } from "react&qu ...
- React + Ts项目搭建
一.安装react+ts npx create-react-app my-app --template typescript 二.安装eslint代码检测 一个好的项目必须有一个规范,所以得安装esl ...
- React + Ts 自定义 日历插件
React + Ts 自定义 日历插件 ant-design 内置了日历组件,但是功能单一并不能够满足项目需求,因此自定义日历组件. 日历算法 一个月最多跨6周,即6*7格式 本月第一天 : fist ...
- arcgis api for js 4.19 尝鲜(React + ts+ arcgis api)
前言 前段时间看到 arcgis api 更新 4.19 版本,而且全面拥抱 ES Modules 开发模式,这无疑是每个 giser 的福音啊:之前的版本基于 dojo 的那种笨重前端框架开发,学习 ...
- [react] 描述下在react中无状态组件和有状态组件的区别是什么?
[react] 描述下在react中无状态组件和有状态组件的区别是什么? 1,无状态组件主要用来定义模板,接收来自父组件props传递过来的数据,使用{props.xxx}的表达式把props塞到模板 ...
- [react] 解释下react中component和pureComponent两者的区别是什么?
[react] 解释下react中component和pureComponent两者的区别是什么? 组件的state或者props更新都会触发render(),同时也会导致子组件render()重新渲 ...
最新文章
- 为在innodb中什么主键用auto_increment效率会提高
- 第八篇、盒子模型和距中的设置方法
- hibernate基本映射文件
- Python遍历目录的4种方法
- callablestatement.setstring会不会将字符串trim_Java String:重要到别人只能当老二的字符串类
- python 字符串翻转
- C++ const限定符和auto类型说明符
- 窗体跳转与传值 02
- flutter框架优缺点_小程序框架全面测评
- 【逐云】阿里“水电煤”背后的人物故事
- MathType编辑器安装(写公式)
- web地图热力图理解
- 【2.25】认识Redis
- 海南大学838信号与系统专业课的感悟
- Centos7(VPS)更改为中国时区并定期同步
- 万亿数字化市场,数据科学为何能扛起“价值担当”?
- ![CDATA[]] 的基本介绍
- GUESS手表全新推出2022年农历新年系列
- linux修改文件创建的时间格式,Linux下修改文件创建时间(修改文件更改时间)
- Android Camera模块(一)
热门文章
- 一文说清,4G语音工牌与WIFI和蓝牙语音工牌的区别
- Android反编译工具的使用(保姆级教程)
- yum 安装Python3
- python处理视频动漫化_太牛逼了!用 Python 实现抖音上的“人像动漫化”特效,原来这么简单!...
- 判断领导是在压榨你,还是在培养你?就看这5点!别被骗了!
- 浅析应用助手省流量升级原理
- symbian下的http连接
- 解决ERROR - unregister mbean error javax.management.InstanceNotFoundException: com.alibaba.druid:type=
- 只允许输入汉字,英文,数字
- -f shell 模糊匹配_Linux模糊搜索神器fzf终极配置