3.0.2 • Published 9 months ago

hatom v3.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

hatom

react数据管理hooks

在我们写的web页面,基本由列表、详情、表单、弹窗组成,尤其是管理后台上最为符合

将这几种场景进行数据抽离,封装成公共库可以大大提升开发效率

使用

实际的业务场景由以下几个组合支撑

  • 当要求数据格式不符合时可在request层统一做处理

列表

涉及到多个项时可以使用useList,例如列表页。我们来看个示例

import {useList} from "hatom";
import {useEffect} from "react";

const ArticlesList = () => {
  const list = useList({
    onGetListData() {
      //需要返回 Promise<{items:any[]}>  
      return request.get('/api/articles')
    }
  });

  useEffect(() => {
    list.getListData();
  }, [])

  if (list.loading) {
    return <div>loading</div>
  }

  return list.items.map(item => {
    return <div>{item.title}</div>
  })
}

如果是有分页也非常方便,只需调用setCurrentPage,会自动在调用onGetListData 中传入分页参数,另外返回的数据要加上统计信息

  • 如果自己的接口分页数据并不是currentPage、totalItems这种格式,建议在request层做统一处理,或者再基于useList进行封装
  • 默认当第二页及以上时会清空之前的数据,如果是移动端下拉追加数据的场景,可以调用setConfig({listDataAppend:true})进行设置
import {useList} from "hatom";
import {useEffect} from "react";

const ArticlesList = () => {
  const list = useList({
    //query为分页信息
    onGetListData(query) {
      //需要返回 Promise<{items: any[];totalItems: number;}>  
      return request.get('/api/articles', {
        currentPage: query.currentPage,
        pageSize: query.pageSize
      })
    }
  });

  useEffect(() => {
    const main = async () => {
      await list.setCurrentPage(1)

      console.log(list.pagination.totalPage)
    }

    main()
  }, [])
  //...
}

useDetail

涉及到单个项时可以使用useDetail,例如详情页

import {useDetail} from "hatom";
import {useEffect} from "react";

const ArticleDetail = () => {
  const detail = useDetail({
    onGetDetailData() {
      return request.get('/api/article', {
        id: router.params.id
      })
    }
  });

  useEffect(() => {
    detail.getDetailData();
  }, [])

  if (detail.loading) {
    return <div>loading</div>
  }

  return <div>{detail.item.title}</div>
}

useForm

涉及到表单时可以使用useForm

  • form.fields是表单中当前的值,form.sediments是调用submit后form.fields的数据拷贝,例如筛选表单只有点击筛选按钮才传给后端
import {useForm} from "hatom";
import {useEffect} from "react";

const ArticleForm = () => {
  const form = useForm({
    fields: {
      title: ""
    },
    onSubmit() {
      if (!form.sediments.title) {
        return alert('请输入标题')
      }
      return request.post('/api/article', {
        ...form.sediments
      })
    }
  });

  return <div>
    <Input value={form.fields.title} onChange={e => {
      form.setFields({
        title: e.target.value
      })
    }}></Input>
    <Button loading={form.loading} onClick={() => {
      form.submit();
    }
    }>提交</Button>
  </div>
}

如果是有筛选和列表组合的场景,只需要监听form.sediments改变时调用list.setCurrentPage(1) 重新请求数据,注意在请求参数里要加上筛选条件form.sediments

import {useForm} from "hatom";
import {useEffect} from "react";

const ArticlesList = () => {
  const form = useForm({
    fields: {
      title: ""
    }
  });
  const list = useList({
    onGetListData(query) {
      return request.get('/api/articles', {
        ...query,
        ...form.sediments
      })
    }
  });


  useEffect(() => {
    list.setCurrentPage(1)
  }, [form.sediments]);

  //return ...
}

与antd组件结合

import {useForm} from "hatom";
import {Form} from 'antd';

const ArticleForm = () => {
  const [antdform] = Form.useForm();
  const form = useForm({
    fields: {
      title: ""
    },
    onSetFields() {
      antdform.setFieldsValue(form.fields)
    },
    async onSubmit() {
      //... 这里可以发送数据给后端
      console.log(form.sediments)
    },
  });

  useEffect(() => {
    //初始值  
    form.setFields({
      title: '111'
    });
  }, []);

  return (
    <Form
      form={antdform}
      onFinish={() => {
        form.submit();
      }}
      onValuesChange={(changedValues) => {
        form.setFields(changedValues)
      }}>
      <Form.Item
        label="标题"
        name="title"
      >
        <Input/>
      </Form.Item>
    </Form>
  )
}

useModal

涉及到弹窗时可以使用useModal

  • 显示弹窗时传的数据都可以放到第一个参数里,然后在modal.payload中拿到
import {useModal} from "hatom";
import {useEffect} from "react";

const ArticleModal = () => {
  const modal = useModal()

  return <div>
    <div onClick={() => {
      modal.showModal({
        title: "标题"
      })
    }
    }>显示
    </div>
    <Modal
      title={modal.payload.title || ''}
      visible={modal.visible}
      onClose={modal.hideModal}
    ></Modal>
  </div>
}

很多时候是在父组件里调用展示弹窗,这时子组件可以使用useImperativeHandle把对应展示弹窗的方法透出去

import {useModal} from "hatom";
import React, {useImperativeHandle, useRef} from "react";

const ArticleModal = React.forwardRef((_props, ref) => {
  const modal = useModal()

  useImperativeHandle(ref, () => {
    return modal;
  })

  return <Modal
    title={modal.payload.title || ''}
    visible={modal.visible}
    onClose={modal.hideModal}
  ></Modal>
})

const ArticlesList = () => {
  const modelRef = useRef<ReturnType<typeof useModal>>()

  return <React>
    <div onClick={() => {
      if (modelRef.current) {
        modelRef.current.showModal({
          title: "标题"
        })
      }
    }
    }>显示
    </div>
    <ArticleModal ref={modelRef}></ArticleModal>
  </React>
}

createStore

在跨组件共享数据时使用,本质封装了context

import {createStore} from "hatom";
import {useState} from "react";

const ThemeContext = createStore(() => {
  const [theme, setTheme] = useState('white')

  return {
    theme,
    setDarkTheme() {
      setTheme('dark')
    }
  }
});

const Child = () => {
  const themeContext = ThemeContext.useContext();

  return <div>当前主题:{themeContext.theme}</div>
}

const Middle = () => {
  const themeContext = ThemeContext.useContext();

  return <div>
    <Child></Child>
    <div onClick={() => {
      themeContext.setDarkTheme();
    }
    }>设置为深色主题
    </div>
  </div>
}

const App = () => {
  return <ThemeContext.Provider>
    <Middle/>
  </ThemeContext.Provider>
}

内部使用了useMemo,设置引用类型状态时必须改变变量地址,不然组件可能不会渲染或者获取数据不是最新值

import {createStore} from "hatom";
import {useState} from "react";

const ListContext = createStore(() => {
  const [list, setList] = useState([1])

  return {
    list,
    login() {
      list[1] = 3;
      //这样设置后组件可能不渲染
      setList(list)
      //这样写才有效
      setList([...list])
    }
  }
});

API

useList

创建列表数据管理器

type useList = <I extends Record<string, any> = any>(options: {
  onGetListData: (query: {
    currentPage: number;
    pageSize: number;
  }) => Promise<{
    items: I[];
    totalItems?: number;
  }>;
  pageSize?: number
}) => Readonly<{
  loading: boolean;
  //当前页数据有没有完成
  isFinish: boolean;
  items: I[];
  pagination: {
    currentPage: number;
    pageSize: number;
    totalItems: number;
    totalPage: number;
  };
} & {
  getListData: () => Promise<void>;
  setCurrentPage: (currentPage: number) => Promise<void>;
  setPageSize: (pageSize: number) => Promise<void>;
  //修改列表数据
  setItems: (callback: (item: I, index: number, array: I[]) => I) => void;
}>

useDetail

创建详情数据管理器

type useDetail = <I extends Record<string, any> = any>(options: {
  onGetDetailData: () => Promise<I>;
}) => Readonly<{
  loading: boolean;
  item: I;
} & {
  getDetailData: () => Promise<void>;
}>

useForm

创建表单数据管理器

type useForm = <F extends Record<string, any> = any>(options: {
  fields: F;
  onSubmit?: (() => void | Promise<void>);
  onReset?: (() => void);
  onSetFields?: ((fields: Partial<F>) => void);
}) => Readonly<{
  loading: boolean;
  fields: F;
  sediments: F;
} & {
  setFields: (fields: Partial<F>) => void;
  submit: () => Promise<void>;
  reset: () => Promise<void>;
}>

useModal

创建弹窗数据管理器

type useModal = <P extends Record<string, any> = any>() => Readonly<{
  visible: boolean;
  payload: P;
} & {
  showModal: (payload?: P) => void;
  hideModal: () => void;
}>

createStore

创建一个数据管理器

type createStore = <D extends Record<string, any> = any>(getDataCallback: (update: () => void) => D) => {
  _Context: React.Context<D>;
  Provider: (props: {
    children: React.ReactElement;
  }) => JSX.Element;
  useContext: () => D;
}

setConfig

设置配置

type setConfig = (config: Partial<{
  listDataAppend: boolean;
}>) => void;
3.0.2

9 months ago

3.0.1

9 months ago

3.0.0

9 months ago

2.0.4

1 year ago

2.0.3

1 year ago

2.0.2

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago