zoukankan      html  css  js  c++  java
  • [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
  • 相关阅读:
    Cypress自动化框架笔记
    SSH 用公钥免密登录,需要改文件权限
    python函数的可变参数 *和**
    Socket进程处理被中断的系统调用及Accept函数返回EINTR错误处理 (转)
    perror 与 strerror
    perror 函数
    strerror 函数
    getch ()函数 (来自百度百科)
    C语言运算符及其优先级汇总表口诀
    第四章
  • 原文地址:https://www.cnblogs.com/Answer1215/p/10393228.html
Copyright © 2011-2022 走看看