npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hatom

v3.0.2

Published

react数据管理hooks

Downloads

8

Readme

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;