• [React] Refactor a Class Component with React hooks to a Function


    We have a render prop based class component that allows us to make a GraphQL request with a given query string and variables and uses a GitHub graphql client that is in React context to make the request. Let's refactor this to a function component that uses the hooks useReducer, useContext, and useEffect.

    Class Based Component:

    import {Component} from 'react'
    import PropTypes from 'prop-types'
    import isEqual from 'lodash/isEqual'
    import * as GitHub from '../../../github-client'
    
    class Query extends Component {
      static propTypes = {
        query: PropTypes.string.isRequired,
        variables: PropTypes.object,
        children: PropTypes.func.isRequired,
        normalize: PropTypes.func,
      }
      static defaultProps = {
        normalize: data => data,
      }
      static contextType = GitHub.Context
    
      state = {loaded: false, fetching: false, data: null, error: null}
    
      componentDidMount() {
        this._isMounted = true
        this.query()
      }
    
      componentDidUpdate(prevProps) {
        if (
          !isEqual(this.props.query, prevProps.query) ||
          !isEqual(this.props.variables, prevProps.variables)
        ) {
          this.query()
        }
      }
    
      componentWillUnmount() {
        this._isMounted = false
      }
    
      query() {
        this.setState({fetching: true})
        const client = this.context
        client
          .request(this.props.query, this.props.variables)
          .then(res =>
            this.safeSetState({
              data: this.props.normalize(res),
              error: null,
              loaded: true,
              fetching: false,
            }),
          )
          .catch(error =>
            this.safeSetState({
              error,
              data: null,
              loaded: false,
              fetching: false,
            }),
          )
      }
    
      safeSetState(...args) {
        this._isMounted && this.setState(...args)
      }
    
      render() {
        return this.props.children(this.state)
      }
    }
    
    export default Query

    Conver props:

     // From 
    static propTypes = {
        query: PropTypes.string.isRequired,
        variables: PropTypes.object,
        children: PropTypes.func.isRequired,
        normalize: PropTypes.func,
      }
      static defaultProps = {
        normalize: data => data,
      }
    
    // To:
    
    function Query ({query, variables, children, normalize = data => data}) {
    
    }

    Conver Context:

    // From
    static contextType = GitHub.Context
    ...
    const client = this.context
    
    // To:
    import {useContext} from 'react'
    
    function Query ({query, variables, children, normalize = data => data}) {
      const clinet = useContext(GitHub.Context)
    }

    Conver State:

    I don't like to cover each state prop to 'useState' style, it is lots of DRY, instead, using useReducer is a better & clean apporach.

    // From
    state = {loaded: false, fetching: false, data: null, error: null}
    
    //To:
      import {useContext, useReducer} from 'react'
      ...
      const [state, setState] = useReducer(
        (state, newState) => ({...state, ...newState}), 
        defaultState)

    Conver side effect:

    // From:
      componentDidMount() {
        this._isMounted = true
        this.query()
      }
    
      componentDidUpdate(prevProps) {
        if (
          !isEqual(this.props.query, prevProps.query) ||
          !isEqual(this.props.variables, prevProps.variables)
        ) {
          this.query()
        }
      }
    
      componentWillUnmount() {
        this._isMounted = false
      }
    
      query() {
        this.setState({fetching: true})
        const client = this.context
        client
          .request(this.props.query, this.props.variables)
          .then(res =>
            this.safeSetState({
              data: this.props.normalize(res),
              error: null,
              loaded: true,
              fetching: false,
            }),
          )
          .catch(error =>
            this.safeSetState({
              error,
              data: null,
              loaded: false,
              fetching: false,
            }),
          )
      }
    
    // To:
    
      useEffect(() => {
        setState({fetching: true})
        client
          .request(query, variables)
          .then(res =>
            setState({
              data: normalize(res),
              error: null,
              loaded: true,
              fetching: false,
            }),
          )
          .catch(error =>
            setState({
              error,
              data: null,
              loaded: false,
              fetching: false,
            }),
          )
      }, [query, variables]) // trigger the effects when 'query' or 'variables' changes  

    Conver render:

    // From:
      render() {
        return this.props.children(this.state)
      }
    
    // To:
    function Query({children ... }) {
    
     ...
     return children(state);    
    }

    -----

    Full Code:

    import {useContext, useReducer, useEffect} from 'react'
    import PropTypes from 'prop-types'
    import isEqual from 'lodash/isEqual'
    import * as GitHub from '../../../github-client'
    
    function Query ({query, variables, children, normalize = data => data}) {
      const client = useContext(GitHub.Context)
      const defaultState = {loaded: false, fetching: false, data: null, error: null}
      const [state, setState] = useReducer(
        (state, newState) => ({...state, ...newState}),
        defaultState)
      useEffect(() => {
        setState({fetching: true})
        client
          .request(query, variables)
          .then(res =>
            setState({
              data: normalize(res),
              error: null,
              loaded: true,
              fetching: false,
            }),
          )
          .catch(error =>
            setState({
              error,
              data: null,
              loaded: false,
              fetching: false,
            }),
          )
      }, [query, variables]) // trigger the effects when 'query' or 'variables' changes
      return children(state)
    }
    
    export default Query
  • 相关阅读:
    Android AlertDialog警告对话框实现
    Android状态栏通知Status Bar Notification
    Android spinner控件的实现
    Winform之UI后台线程
    Winform之自定义控件
    WebForm原理,aspx服务器端与客户端源码比较
    IHttpModule之闲扯
    [算法]方正面试题:N×N矩阵螺旋打印输出
    DOTA版设计模式——工厂方法
    Window服务
  • 原文地址:https://www.cnblogs.com/Answer1215/p/10393228.html
Copyright © 2020-2023  润新知