React Hook 實戰指南!(4)

業務邏輯與其餘Hooks

在這一篇內容中,咱們一塊兒來看一下其餘的Hooks在業務中的使用。
首先,咱們先來研究一下Todolist根組件的實現,首先,TodoList根組件須要利用useTodolistStoreContext來獲取到items和actions,由於若是items在初始狀態沒有數據的話,須要調用actions中的getInitialItems方法來獲取數據:javascript

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能頂三個,可以用嗎?真的夠用了,不信一塊兒來看:java

  1. 組件還未渲染前的動做?直接在return 組件結構上面執行就能夠 (componentWillMount)
  2. 組件初次渲染後的動做?這個時候useEffect()傳入的函數會執行喲 (componentDidMount)
  3. 組件props或者state變化後的動做?這個時候useEffect()傳入的函數也會執行喲 (componentDidUpdate)
  4. 組件銷燬前的動做?這個時候useEffect()傳入的函數固然會執行啦 (componentWillUnmount)

有這些就足夠了呢,並且useEffect能夠屢次使用,每次第二個參數傳入監聽的數據後,能夠根據監聽的數據是否變化決定是否執行,多麼智能。react

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的使用就簡單多了,傳個參數就能夠了。api

咱們上面的代碼也很簡單,就是當items數據沒有的時候就調用actions的方法去獲取下初始的數據。數組

接下來由於要編輯item,因此咱們須要一個用來存放編輯的item的狀態,並且爲了控制TodoListCAE組件(左側劃出的抽屜)顯示隱藏,因此也須要一個狀態來控制,那麼在TodoList這樣的函數組件中如何構建一個狀態呢?antd

useState這個Hook終於閃亮登場了,它的使用很是簡單,前面以及介紹過了。dom

各個組件的實現

下面直接放出最終的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狀態來控制當前的頁數:ui

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的方法:this

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使用方面的幹活我會慢慢放上來的,再見啦。

相關文章
相關標籤/搜索