1. 首页

React Hook 实战指南!(4)

业务逻辑与其他Hooks

在这一篇内容中,我们一起来看一下其他的Hooks在业务中的使用。
首先,我们先来研究一下Todolist根组件的实现,首先,TodoList根组件需要利用useTodolistStoreContext来获取到items和actions,因为如果items在初始状态没有数据的话,需要调用actions中的getInitialItems方法来获取数据:


const TodoListPage = (props) => { const [items, actions] = useTodolistStoreContext('items') useEffect(() => { if (!items.length) actions.getInitialItems() }) // ... 忽略其他 }

useEffect

此时我们看到useEffect这个Hook,它是做什么的呢?很简单,useEffect其实就可以理解为函数组件的生命周期函数,因为函数组件不是类组件,无法使用componentDidMount等生命周期钩子,所以Hook提供useEffect Hook来监听数据的变化,来替代生命周期钩子,它其实是componentDidMount、componentDidUpdate、componentWillUnmount三个钩子函数的组合体,用起来确实方便来很多,但是类组件可以是有十来个钩子的,就算useEffect能顶三个,能够用吗?真的够用了,不信一起来看:

  1. 组件还未渲染前的动作?直接在return 组件结构上面执行就可以 (componentWillMount)
  2. 组件初次渲染后的动作?这个时候useEffect()传入的函数会执行哟 (componentDidMount)
  3. 组件props或者state变化后的动作?这个时候useEffect()传入的函数也会执行哟 (componentDidUpdate)
  4. 组件销毁前的动作?这个时候useEffect()传入的函数当然会执行啦 (componentWillUnmount)

有这些就足够了呢,而且useEffect可以多次使用,每次第二个参数传入监听的数据后,可以根据监听的数据是否变化决定是否执行,多么智能。


const Example = (props) => { let [count, setCount] = useState(0) // 这样,初始化的时候,effect会执行,而且无论是count变化还是props变化,effect也都会执行 useEffect(() => { }) // 这样表示下面的这个effect什么都不监听,初始的执行一次后,不管props还是state变化都不再执行了 useEffect(() => { }, []) // 除了初次执行,props变化会执行 useEffect(() => { }, [props]) // 除了初次执行,props.title变化会执行 useEffect(() => { }, [props.title]) // 除了初次执行,props.title变化会执行,而且count变化也会执行 useEffect(() => { }, [props.title, count]) }

啧啧啧,真是甩往常的update阶段的钩子函数五条街都不知道,比如如果我们想要在某个数据变化后才去做某个动作,在往常的钩子中需要手动判断该数据是否变化: if (state.a !== this.state.a) { ….. }, 而useEffect的使用就简单多了,传个参数就可以了。

我们上面的代码也很简单,就是当items数据没有的时候就调用actions的方法去获取下初始的数据。

接下来因为要编辑item,所以我们需要一个用来存放编辑的item的状态,而且为了控制TodoListCAE组件(左侧划出的抽屉)显示隐藏,所以也需要一个状态来控制,那么在TodoList这样的函数组件中如何构建一个状态呢?

useState这个Hook终于闪亮登场了,它的使用非常简单,前面以及介绍过了。

各个组件的实现

下面直接放出最终的TodoList根组件:


import { useTodolistStoreContext } from '@/hooks/todolist' import TodoListContent from './particles/TodoListContent' import TodoListDes from './particles/TodoListDes' import TodoListCAE from './particles/TodoListCAE' const TodoListPage = (props) => { // 取出store的items数据和actions const [items, actions] = useTodolistStoreContext('items') // 建立控制右侧抽屉显示的状态,默认值为false let [visible, setVisible] = useState(false) // 建立用来编辑的item,默认值为null let [editItem, setEditItem] = useState(null) // 当items数据没有的时候就去初始的获取 useEffect(() => { if (!items.length) actions.getInitialItems() }) // 右侧抽屉显示隐藏动作 function toggleVisible () { setVisible((visible) => !visible) setEditItem(null) } // 编辑动作 function editAction (item) { setVisible((visible) => !visible) setEditItem(item) } return ( <div className="page-container todolist-page"> <Typography> <Typography.Title level = {3}>待办事项</Typography.Title> <TodoListDes/> {/* 新建按钮 */} <Button onClick={toggleVisible} className="create-btn" type="primary" shape="circle" icon="plus" size="large" /> </Typography> {/* 新建和编辑的右侧抽屉 */} <TodoListCAE editItem={editItem} visible={visible} toggleVisible={toggleVisible}/> {/* 显示items的内容 */} <TodoListContent editAction={editAction} toggleVisible={toggleVisible}/> </div> ) } export default TodoListPage

TodoListContent组件也很简单,因为需要实现分页,所以也需要构建page状态来控制当前的页数:

import { useTodolistStoreContext } from '@/hooks/todolist'

import TodoListItem from './TodoListItem'

const TodoListContent = memo((props) => {
  // 取出items待用
  let [items] = useTodolistStoreContext('items')
  // 构建page状态代表当前页数,默认值为1
  let [page, setPage] = useState(1)
  // 每页8条数据
  let pageSize = 8

  // 根据page、pageSize及items来渲染TodoListItem组件的方法
  function renderItems () {
    // 没有数据显示为空
    if (!items.length) return <Empty />
    // 准备渲染的分页后的items
    let renderItems = items.slice(
      (page - 1) * pageSize,
      page * pageSize
    )
    return renderItems.map((item) => (
      <Col className="todolist__item-col" key={item.id} span={24 / (pageSize / 2)}>
        <TodoListItem editAction={props.editAction} info={item}/>
      </Col>
    ))
  }

  return (
    <div className="todolist-content">
      <Row gutter={16} className="todolist-content__container">
        <Suspense fallback={<Spin/>}>{ renderItems() }</Suspense>
      </Row>
      <Pagination
        total={items.length}
        showTotal={(total, range) => `${range[0]}-${range[1]} of ${total} items`}
        pageSize={pageSize}
        current={page}
        onChange={(page) => setPage(page)}
      />
    </div>
  )
})

export default TodoListContent

TodoListItem组件更多的是数据的展示,它需要接受到对于item的信息,得到items,还需要接受到父级组件传入的编辑item的动作,以及actions中完成、删除item的方法:


import { useTodolistStoreContext } from '@/hooks/todolist' const TodoListItem = memo((props) => { const [state, actions] = useTodolistStoreContext() // item的信息 let { title, description, finished, id } = props.info // 根据是否完成来确定主题颜色 let color = finished ? '#1890ff' : '#ccc' // 完成此todo function finishAction () { actions.finishTodo({ id }) } // 删除此todo function deleteAction () { actions.deleteTodo({ id }) } // 编辑此todo function editAction () { props.editAction(props.info) } return ( <Card className={`todolist__item ${ finished ? 'finished' : '' }`} title={ title } extra={<Icon type="check-circle" style={{ fontSize: '24px', color }}/>} actions={[ <Icon onClick={finishAction} type="check" key="check" />, <Icon onClick={editAction} type="edit" key="edit" theme="filled" />, <Icon onClick={deleteAction} type="delete" key="delete" theme="filled" />, ]} > <Typography.Paragraph ellipsis={{ rows: 5 }}>{ description }</Typography.Paragraph> </Card> ) }) export default TodoListItem

还有一个TodoListDes.js组件,这个组件只需要展示一些计数信息即可:


import { useTodolistStoreContext } from '@/hooks/todolist' const TodoListDes = memo(() => { let [detail] = useTodolistStoreContext('todoDetail') return ( <Typography.Paragraph> { detail.description } </Typography.Paragraph> ) }) export default TodoListDes

Ok,接下来我们来看一下TodoListCAE.js的实现


import React, { useEffect, useState, useRef } from 'react' import { Button, Drawer, Form, Input } from 'antd'; import { useTodolistStoreContext } from '@/hooks/todolist' const { TextArea } = Input // 这是我们的表单组件,专门用来新建和编辑item const TodoListFormUI = (props) => { const [state, actions] = useTodolistStoreContext() const { toggleVisible, form } = props const { getFieldDecorator } = form // 取消的动作 function handleCancel () { toggleVisible() form.resetFields() } // 保存的动作 function handleOk () { // 保存的时候进行数据验证 form.validateFields((err, values) => { if (!err) { // 数据验证通过后,如果editItem不存在说明是新建模式 if (!props.editItem) { // 调用actions中的新建方法 actions.createTodoItem({ item: values }) } else { // 如果是编辑的话,调用actions中的编辑动作 actions.updateTodoItem({ item: { ...values, id: props.editItem.id } }) } // 完成后隐藏抽屉 toggleVisible() // 重置表单 form.resetFields() } }) } // 监听editItem的变化 useEffect(() => { // 如果editItem存在,说明是要编辑了 if (props.editItem) { // 取出要编辑的item的信息,同步到form的input value值 let { title, description } = props.editItem form.setFieldsValue({ title, description }) } else { // 新建模式的时候重置表单 form.resetFields() } }, [props.editItem]) return ( <Form layout="vertical"> <Form.Item label="Title"> {getFieldDecorator('title', { rules: [{ required: true, message: '事项标题不能为空!' }], })(<Input />)} </Form.Item> <Form.Item label="Description"> {getFieldDecorator('description', { rules: [{ required: true, message: '事项内容不能为空!' }], })(<TextArea autosize={{ minRows: 15 }} type="textarea" />)} </Form.Item> <Form.Item > <Button onClick={handleOk} type="primary" > 保存 </Button> <Button type="default" onClick={handleCancel}> 取消 </Button> </Form.Item> </Form> ) } const TodoListForm = Form.create({ name: 'todolist-form' })(TodoListFormUI) // 新建和编辑Item的组件 const TodoListCAE = (props) => { // 接受到的是否隐藏的状态和切换隐藏状态的动作 const { visible, toggleVisible } = props // 抽屉上方展示的title状态 let [title, setTitle] = useState('') // 准备标记下面的TodoListForm,标记后放到form中去 const form = useRef(null) // 根据editItem的变化确定是编辑模式还是新建模式,然后更新title useEffect(() => { setTitle(props.editItem ? '编辑待办事项' : '新增一条待办事项') // 根据编辑模式还是新建模式来设置表单的值或者重置表单 // if (form.current) { // if (props.editItem) { // let { title, description } = props.editItem // form.current.setFieldsValue({ title, description }) // } else { // form.current.resetFields() // } // } }) return ( <Drawer width="500" title={title} placement="right" closable={false} onClose={toggleVisible} visible={visible} > <TodoListForm editItem={props.editItem} ref={form} toggleVisible = {toggleVisible}/> </Drawer> ) } export default TodoListCAE

useRef

大家可以看到在TodoListCAE中有useRef钩子的使用,这个专门用来做ref标记的,在类组件中我们只需要将标记的元素或者子组件放入到this上,例如:


return ( <div> <input ref = {inp => this.inp = inp} /> <Child ref = {child => this.child = child}/> </div> )

然后就可以在任意的钩子函数中利用this.inp和this.child来获取到标记的input-dom元素和Child子组件了,但是在函数组件中没有this怎么挂载呢?没错,就是用useRef来生成一个标记数据,然后将要挂载的子组件或Dom元素挂载在这个标记数据上就可以了。

上面在TodoListCAE中我们可以利用useRef构建一个form标记,然后用form来标记表单子组件,然后就可以在子组件中调用form.XXX来调用到子组件的一些API方法了。

注释掉是因为没有这么做的必要(虽然可以这么干),我们在Form子组件中自己监听数据变化来调用重置和设置值的api方法就可以了。

后语

到这里位置,我们的Todolist实例也就编写完成了,我们一共研究了useState/useEffect/useReducer/useRef/Custom Hook的实现方式,小伙伴们也要加油哟,其实这些东西都在官方文档上,要好好看文档哟。

后续还有其他有关于React Hook使用方面的干活我会慢慢放上来的,再见啦。

作者:半盏屠苏
链接:https://juejin.im/post/5d78b086f265da03c8154d4b

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:React Hook 实战指南!(4)

链接:https://www.javascriptc.com/3830.html

« 关于这些天杭州各厂面试汇总(从JavaScript各种原理到框架源码
深夜被女友手把手入门webpack的感觉»
Flutter 中文教程资源

相关推荐

QR code