ps:想要获得更好的阅读体验,请访问原地址:(https://www.yuque.com/zibuyu/fed/ognwv0),该作者是个能力很强的前端开发。文章转载已获得许可。
React在16.7-alpha版本中新增了React的一些新特性,如Hook。
在这里主要想分享一下关于Hook使用
About Hook
- useState() 让你定义一个state变量
- useEffect() 让你定义一个副作用
- useContext() 让你读取某些Context
PS: 了解上面三个 api 就可以对Hook进行基本的使用了。 类似于官方的声明可以直接去官网查看。下面直接进入实战模式。
Practice
这里我们先说明我们要做的是什么, 我们在这里最终目的是要做一个由Hook制作的一个异步Table组件,Table使用的是 **Ant-design **里封装好的组件, 而cli用 **create-react-app **官方自带吧。因为侧重 hook 所以我们不会涉及到redux之类的,所以也不必要徒增不必要的代码量。 then let's do it。
Mock Data
第一步,首先我们要构造后台的数据,或者自己找一个可以进行mock的数据就可以。这个尽可能模仿后台,我这里使用的就用 ant-design-pro 提供的分页。https://preview.pro.ant.design/api/rule?currentPage=1&pageSize=10 可以在postman搜索即可 ,浏览器打开不正常。估计是做了额外的处理。但是有个问题就是跨域问题解决不了,我这边基于easy-mock又做了一个 接口, 理论上两个行为是一致的。
主要后台和前端配合下 前端传 当前页码 (currentPage) 和 展示数量 (pageSize) 即可。 后台返回需要有 总页数。 因为我们要做如下这个情况就必须得要有总页数。
Like this. ↓
imageanyway. 数据有了就可以下一步了。
Font-end
构建目录
-
create-react-app 构建出基本的react项目 这个可以看官方文档
-
对package.json进行修改,把 react , react-dom 两个修改成 v16.8.0-alpha.1, 然后重新安装
-
这里还需要把 antd 加上 毕竟我们也要用到他们的Table组件
-
还有一个请求这里直接用原生的fetch就可以了
贴一下目前的package.json
<pre class="cm-s-default" style="box-sizing: border-box; font-family: inherit; font-size: inherit; margin: 0px; overflow: visible; padding: 0px; border-radius: 0px; border-width: 0px; background: rgba(0, 0, 0, 0); white-space: pre; overflow-wrap: normal; line-height: inherit; color: inherit; z-index: 2; position: relative; -webkit-tap-highlight-color: transparent; font-variant-ligatures: contextual;">{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^3.13.0",
"react": "v16.8.0-alpha.1",
"react-dom": "v16.8.0-alpha.1",
"react-scripts": "2.1.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}</pre>
接下来我们删除目前不需要用到的页面,并且把 server-worker 去掉, 最后简化到如下即可。 代码可在github上预览。
接下来第一次尝试
异步获取数据并且渲染到页面
首先这里有一个关键的问题 , **异步 **在一开始,我们就得明确hook中使用带有副作用的都得使用 useEffect() 进行定义, 异步也是副作用的一种。因此,我们获取数据的时候应该是使用 useEffect() 代码走一个
yap,我们可以得到我们想要的数据了!
image解释一下这里, useEffect() 这个非常强大, 哈哈哈。 不愧是在React发布会上Ryan特别点了一下,真好用。哈哈
这里主要是容纳了 componentDidMount 和 componentDidUpdate return 的时候就是 组件 umount的时机, 因此 subscribe在useEffect中,但return的时候就应该把他们给 unsubscribe。
Ok. just fine.
下一步。
我们已经知道如何得到数据 , 那么我们把它塞到我们的Table组件中。试一下
失败
image这里错误的原因很简单,因为数据在更新之后并没有处罚需要 re-render 的条件,因此我们加上 useState()
image数据请求成功。并且渲染成功! but,这里有一个问题。为什么我们会有这么多条请求?如果不报错甚至会出现死循环? 因为 调用的位置 出现问题了! 想想我们以前写的react, 只有一次的触发时机。 那就是 componentWillMount 或者 componentDidMount 那么 ,这个位置在哪里呢?
useEffect() 就因此而来。
image没有任何问题! 但是,这里我多做了一步,就是 给useEffect添加了第二个参数
useEffect() 的第二个参数就是专门做数据对比,它的入参是一个变量。只有在变量指向的数据进行修改后,进行对比发现不一致时,才会重新进行渲染。
好的,现在才是正题啊!
封装异步Table组件
定义入参方法
展示的columns, 请求后台api, 传后台参数, 后台返回的数组的字键名称
function asyncTableList(columns, queryAction, params, cgiKey) {
// do something
}
整套代码如下
<pre class="cm-s-default" style="box-sizing: border-box; font-family: inherit; font-size: inherit; margin: 0px; overflow: visible; padding: 0px; border-radius: 0px; border-width: 0px; background: rgba(0, 0, 0, 0); white-space: pre; overflow-wrap: normal; line-height: inherit; color: inherit; z-index: 2; position: relative; -webkit-tap-highlight-color: transparent; font-variant-ligatures: contextual;">/**
* 异步table
* 组件使用方法
*
* import renderAsyncTable from './this.file'
*
* const asyncTable = renderAsyncTable(展示的columns, 请求后台api, 传后台参数, 后台返回的数组的字键名称)
*
* function App() {
* const [query] = useState()
* const columns = [...]
* const asyncTable = renderAsyncTable(columns, api, query, listName )
* return (
* <div>
* {asyncTable}
* </div>
* )
* }
*/
import React, { useEffect, useReducer } from 'react'
import Table from 'antd/lib/table';
function useAsyncTable (columns, queryAction, params, listName) {
const paginationInitial = {
current: 1,
pageSize: 10
}
const [state, dispatch] = useReducer(
(state, action) => {
const { payload } = action
switch (action.type) {
case 'TOGGLE_LOADING':
return { ...state, loading: !state.loading }
case 'SET_QUERY':
return {
...state,
query: payload.params,
pagination: paginationInitial
}
case 'SET_PAGINATION':
return { ...state, pagination: payload.pagination }
case 'SET_DATA_SOURCE':
return { ...state, dataSource: payload.dataSource }
default:
return state
}
},
{
loading: false,
query: null,
pagination: paginationInitial,
dataSource: []
}
)
function handleTableChange (event) {
if (event) {
const { current } = event
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
function doQuery () {
dispatch({
type: 'TOGGLE_LOADING'
})
const { current, pageSize } = state.pagination
const pagination = {
current,
pageSize
}
queryAction({
...state.query,
...pagination
})
.catch(err => {
dispatch({
type: 'TOGGLE_LOADING'
})
return {}
})
.then((payload) => {
if(payload.pagination && payload.list) {
const { pagination: { total } } = payload
console.log('total', total)
dispatch({
type: 'TOGGLE_LOADING'
})
if (!state.pagination.total) {
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: { ...state.pagination, total }
}
})
}
dispatch({
type: 'SET_DATA_SOURCE',
payload: {
dataSource: payload[listName]
}
})
}
})
}
// cDM cDU
useEffect(
() => {
if (params && JSON.stringify(params) !== JSON.stringify(state.query)) {
dispatch({
type: 'SET_QUERY',
payload: {
params
}
})
} else {
doQuery()
}
},
[params, state.pagination.current, state.query]
)
return (
<Table
columns={columns}
rowKey={record => record.key}
pagination={state.pagination}
dataSource={state.dataSource}
loading={state.loading}
onChange={handleTableChange}
/>
)
}
export default useAsyncTable</pre>
useReducer
这里解释一下:
在React官网的Hook中也有用到useReducer这种方法! 怎么说呢? 用hook的方式写一个redux吧。 用法和redux一样。state只能被dispatch的数据进行修改。 dispatch用type来区分修改state里的哪个数据,然后 我这边用payload 带数据进去。修改 state 内部数据。
每次做diff的之后 只拿 透传过来的 params 和 存在reducer里的 query做对比,如果有修改就进行修改。
没有的话就是调用请求。
这里调用请求可以理解为 :
- 只有在 current 页面不一致的时候调用
- 只有在query更改过后进行修改
而params在这里只是作为一个触发修改query的操作,并不会直接进行请求。
调用页面
调用相对简单。 我们需要传入的参数只有4个
- columns的展示
- 异步拉取的接口
- 其他query, 这个query可能需要解析一下, 因为不是所有的异步拉取只有 pageSize 和 current 两个参数。 它们还有可能是 cityId 为 10000 或者 name是小明 也有可能是集合 如 城市A区,名字小白。这样,我们异步在使用异步拉取的接口 就可以不单单的延申为 pageSize 和 current这么单调。 它也可以combine参数后得到更好的拓展。
- listName。 这个主要是为了不同的后台,有可能它是比较泛的名称如 lists 或者 beanList 等等,但也有可能是 cities,topics。 因此也用透传的方式。 更加强壮。
<pre class="cm-s-default" style="box-sizing: border-box; font-family: inherit; font-size: inherit; margin: 0px; overflow: visible; padding: 0px; border-radius: 0px; border-width: 0px; background: rgba(0, 0, 0, 0); white-space: pre; overflow-wrap: normal; line-height: inherit; color: inherit; z-index: 2; position: relative; -webkit-tap-highlight-color: transparent; font-variant-ligatures: contextual;">import React, {useState} from 'react';
import renderAsyncTable from './asyncTable'
import getAjax from './utils/ajax'
function App() {
const [query] = useState()
const columns = [
{
title: '规则名称',
dataIndex: 'name',
},
{
title: '描述',
dataIndex: 'desc',
},
{
title: '服务调用次数',
dataIndex: 'callNo',
render: val => `${val} 万`,
// mark to display a total number
needTotal: true,
},
{
title: '上次调度时间',
dataIndex: 'updatedAt',
},
];
const asyncTable = renderAsyncTable(columns, (query) => getAjax('o2po/data', query), query, 'list' )
return (
<div style={{width: 600, margin: '30px auto'}}>
{asyncTable}
</div>
)
}
export default App;
</pre>
整套流程就可以了。
image
网友评论