typescript + react 项目开发体验之 react

目录

  • 起手式
  • typescript
  • react一些小姿势
  • react的状态管理

整体框架的理解

​ 电影是由一个个帧构成,播放的过程就是帧的替换。react 官方文档元素渲染模块有句这样的话:React 元素都是不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。

​ 结合实际去理解,就是当页面构建,网页就形成了初始帧,网页上的展现内容是由state去控制,react可以通过setState通过改变状态去做帧的切换,由此实现页面展现效果的变化。为了提升性能,并尽可能快且正确的切换帧,react做了如下优化:

  1. 异步的setState。
  2. 采用diff算法,尽可能快的进行元素比较,并找到相应元素替换。
  3. 引用fiber算法,减少深层级组件对网页响应的影响。

react中使用ts

  1. 组件定义
//我们可以在 /node_modules/@type/React 找到有关Component的定义,我们截取部分
interface Component

extends ComponentLifecycle { } class Component { constructor(props: Readonly

); state: Readonly; // .... } // 可以看出定义一个组件可以通过定义泛型去指定他的props和state类型 interface IDemoProps{ a: string, b: string } interface IDemoState{ c: string, d: string } // 当你定义一个组件时定义了State类型,你至少以下列一种方式指定state属性,否则会报错; class Demo extends Component{ // 方式一 state={ c: 1, d: 2 } constructor(props: IDemoProps){ super(props); // 方式二 this.state = { c: 'c', d: 'd' } props.c // 报错,属性不存在 } render(){ return <>

{this.state.c}
{this.props.a}
} } // 当组件内部不需要使用生命周期钩子,或者组件内不保存自身状态时,可简化成函数式组件 // 同样我们找到定义方式 interface FunctionComponent

{ (props: P & { children?: ReactNode }, context?: any): ReactElement | null; // ... } const Demo = (props: IDemoProps)=>{ return <>

{this.state.c}
{this.props.a}
} // pureComponent 在写组件的过程中同学会用到 pureComponent 去提升组件的性能 // 从描述上来看它和普通的Component没有区别 class PureComponent

extends Component { } // 截取一段 pureComponent 源码进行比较发现他就是当组件不存在shouldComponetUpdate这个钩子时,会自己新增一条规则即前后 props 和 state 进行浅比较判断是否需要修改组件。 if (inst.shouldComponentUpdate) { shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } else { if (this._compositeType === CompositeType.PureClass) { // 用 shallowEqual 对比 props 和 state 的改动 // 如果都没改变就不用更新 shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } //在 /node_modules/@type/React 中还可以找到这些,我可以理解成这是component的接口声明方式 interface ComponentClass

extends StaticLifecycle { new (props: P, context?: any): Component; // .... } // sfc fc StatelessComponent 都是 FunctionComponent 的别名。 type ComponentState = any; type SFC

= FunctionComponent

; type StatelessComponent

= FunctionComponent

; type FC

= FunctionComponent

; type ComponentType

= ComponentClass

| FunctionComponent

; // 因此当我们以组件作为参数传递的时候可以使用ComponentType进行声明 复制代码

ps: 当我们用第三方库的时候,不妨看看他们的d.ts文件,这个可能是最好的文档。

  1. 视图的拆分

我们在组件的书写过程中,由于展示视图过于复杂,render函数的时候常常会遇到大段大段的dom和一些复杂的条件渲染。如果把他们都放在同一个函数下会造成组件代码太长,不利于维护,因此拆分就很有必要了。我常常用到以下几种拆分方式:

// 拆分成组件,把大组件拆分成多个小组件,这时候不免一些方法的传递,为了使得 this 绑定当前组件,定义方法时可以采用下面switchItemHandler这种方式,避免再进行一个bind。
//...component dosomthing....
switchItemHandler = (checkItem:checkedItemData)=>{
    this.setState...
}
render(){
        return <>
        
        
        
    
 }
// 有时候逐个参数传递太麻烦了我们可以定义一个返回jsxElement的函数,然后通过call去调用,注意,使用bind、call、apply的函数,在ts中的定义,需要在形参中加入this的类型定义。
export const comfirm = function(this: GoodsBuy){
  return 
确认兑换
} // class GoodsBuy render(){ return
{goodsAndCount.call(this)} {total.call(this)} {comfirm.call(this)}
} 复制代码

一个组件说说HOC 、 props render

在手机端业务中当有长列表,常常需要逐步加载相应的需要展现的内容。页面滚动至底部加载就是其中一个策略。接下来我们就来讲这个功能进行抽离实现组件化。

滚动至底部加载我们可以把这个逻辑进行拆分一下。

  1. 监听滚动条事件的监听。
  2. 数据加载策略。
  3. 具体列表内容的展现。

做过微信小程序的同学可能记得它提供一个onReachBottom上拉触底的钩子,参照这个设计思路我希望我定义组件时,加入一个钩子,在滚动到底部的时候这个钩子会被调用。

// component
scrollInBottom = ()=>{
    // do something..
}
复制代码

通常情况下我们需要去做监听调用。

scrollBottomHandler = ()=>{
    if(document.body.scrollHeight - document.body.scrollTop  <  innerHeight + 10)
        this.scrollInBottom();
}
componentDidMount(){
    document.addEventListener('scroll',this.scrollBottomHandler)
}
componentWillUnmount(){
    document.removeEventListener('scroll', this.scrollBottomHandler);
}
复制代码

我现在想把这个功能抽离,目前有两种思路:

  1. 定义一个具有着三个方法的类,通过 extends 使得现有Component也能具有这个三个方法。
  2. 定义一个接收一个classComponent(具备一个钩子scrollInBottom)的函数,返回一个组件,这个组件进行滚动监听,当滚动到底部的时候调用classComponent 的component方法(这也就是咱们常说的HOC)。

第一种方法有个很吃瘪的地方,就是当前组件如果定义了这三个方法时,会覆盖extends的方法,使得功能失效,需要额外的super操作,这里就不细说了。于是我毅然决然的选择了第二种方式。

// 我们来根据第二种思路来描述这个方法
// 定义一个具有scrollInBottom:()=>void函数作为属性的react组件
type IComponet = {scrollInBottom: ()=>void} & Component;
// 定义一个能获取ref,实例化后能生成具有scrollInBottom的组件。
interface IHasScrollBottomClass

extends ComponentClass{ new (props: P, context?: any): IComponet } // 接下来就是上面思路2的描述了,就不赘述啦。 const scrollBottomLoad = (WrapComponent: IHasScrollBottomClass)=>{ return class extends Component{ subRef: IComponet | null = null scrollBottomHandler = ()=>{ if(!this.subRef)return; if(document.body.scrollHeight - document.body.scrollTop < innerHeight + 10) this.subRef.scrollInBottom(); } componentDidMount(){ document.addEventListener('scroll',this.scrollBottomHandler) } componentWillUnmount(){ document.removeEventListener('scroll', this.scrollBottomHandler); } render(){ return this.subRef = cp} {...this.props} > } } } 复制代码

至此,滚动事件监听功能就已经抽离出来了。接下来我们要抽离加载和具体内容展示。我们码上说话

interface QueryListModel{
  start: number;
  limit: number;
}
// 定义组件接收3个参数
interface ILoadDataAndCheckMoreProps {
    loadFuc: (queryCondition: T & QueryListModel)=>Promise;//数据加载函数
    queryCondition: T;// 除基础模型歪的查询条件
    render: (props: K[])=>ReactElement<{ list: K[]}>; // 渲染列表的方法
}
// 本地相关state分别保存
interface ILoadDataAndCheckMoreState{
    noMore: boolean // 是否还有数据
    queryConditionCombin: T & QueryListModel // 条件列表
    list: K[] // 数据列表
}
const ANYTIME_REQUEST_ITEMNUMBER = 10; // 每次请求他的条数
class LoadDataAndCheckMore extends Component, ILoadDataAndCheckMoreState> {
    constructor(props:ILoadDataAndCheckMoreProps){
        super(props);
        // 初始化3个状态
        this.state = {
            noMore: false,
            queryConditionCombin: this.initQueryCondition(props.queryCondition),
            list: []
        }
    }
    // 初始化状态,为啥这里要是负数呢?往下看。
    initQueryCondition = (props: T)=>{return Object.assign({}, props, {start: -ANYTIME_REQUEST_ITEMNUMBER, limit: 10 })}
    // 数据加载
    loadMore = ()=>{
        // 如果没有数据了,不再加载
        if(this.state.noMore)return;
        // 这就是上面为什么start要为负数
        this.state.queryConditionCombin.start += ANYTIME_REQUEST_ITEMNUMBER;
        // 每次请求之后,并检查还没有更多。
        this.loadListAndCheckNoMore().then((data: K[])=>{
            this.setState({
                list: this.state.list.concat(data)
            })
            Toast.hide();
        })
        // loading相关
        Toast.loading('数据加载中....', 3000)
    }
    loadListAndCheckNoMore = ()=>{
   // 判断条件是取得的数据数量,小于limit。为啥要这样,因为后端没有返回这个字段给我,我就只能这样判断咯。
        return this.props.loadFuc(this.state.queryConditionCombin).then((data:K[])=>{
            this.setState({
                noMore: data && data.length < this.state.queryConditionCombin.limit
            })
            return data;
        })
    }
    // 你懂得,哈哈哈哈哈哈哈哈哈嗝。
    scrollInBottom = ()=>{
        !this.state.noMore && this.loadMore();
    }
    // 当搜索条件变化之后,是不是要从0开始加载呢?
    componentWillReceiveProps(nextProps:ILoadDataAndCheckMoreProps){
        this.setState({
            queryConditionCombin: this.initQueryCondition(nextProps.queryCondition),
            noMore: false,
            list: []
        },this.loadMore)
    }
    // 第一次加载数据喔。
    componentDidMount(){
        this.loadMore()
    }
    render(){
        // 我期望渲染的方式交由业务层面
        return <>
			// 为什么要用render作为函数传递呢?因为如果写成组件你需要
            // const Cp = this.props.render
            // 
            // 这就有点狠难受了,因为props我就想传个数组,但是组件不支持啊,因为他要个对象。
            // 然后你需要重新把他拎出来,根据组件的命名规范,才能重新使用
            {this.props.render(this.state.list)}
    		// 提示提示提示咯
            

{this.state.noMore ? '这里见底啦/(ㄒoㄒ)/~~...' : '数据加载中,请稍后...'}

} } // 最后是导出,为啥要这么写呢? export default ()=> scrollBottom< ILoadDataAndCheckMoreProps, ILoadDataAndCheckMoreState >(LoadDataAndCheckMore) // 我们回看一下scrollBottom方法。 (WrapComponent: IHasScrollBottomClass) // 如果直接返回scrollBottom(LoadDataAndCheckMore),这个T和S会被当成简单的{}。这样就会造成ILoadDataAndCheckMoreProps、ILoadDataAndCheckMoreState的泛型T/K就是空对象,显然是不正确的。 复制代码

就此个人对react组件封装相关的的思路就结束啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦。。。。

转载于:https://juejin.im/post/5c42c6b86fb9a049ac7975d7

你可能感兴趣的:(javascript,后端)