使用钩子将问题从类重构为函数

时间:2019-05-26 19:37:18

标签: reactjs react-hooks

我正在重构一些代码,现在我被卡住了。 原始代码来自tuto: https://github.com/EricLondon/rails5-react-reactstrap-crud-example 那里一切都很好。 我尝试使用钩子的新功能。 我写的新代码失败了。

“原始代码”已通过测试 “新代码”失败

//"New code"
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Container, Row, Col, Alert } from 'reactstrap'
import PostsTable from '../PostsTable'

const Api = require('../../api/Api.js')

export default function Posts() {
  const [posts, setPosts] = useState([])
  const [isLoaded, setIsLoaded] = useState(false)
  const [error, setError] = useState(null)

  useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setIsLoaded(true)
          setPosts([])
          setError(data)
        } else {
          setIsLoaded(true)
          setPosts(data)
        }
      })
  })

  if (error) {

    return (
      <Alert color="danger">
          Error: {error}
      </Alert>
    )

  } else if (!isLoaded) {

    return (
      <Alert color="primary">
          Loading...
      </Alert>
    )

  } else {

    return (
      <Container>
        <Row>
          <Col>
            <PostsTable posts={posts}></PostsTable>
            <Link className="btn btn-primary" to="/posts/new">Add Post</Link>
          </Col>
        </Row>
      </Container>
    )

  }
}



//"Original code"
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { Container, Row, Col, Alert } from 'reactstrap'
import PostsTable from '../PostsTable'

const Api = require('../../api/Api.js')

class Posts extends Component {
  constructor(props) {
    super(props)
    this.state = {
      posts: [],
      isLoaded: false,
      error: null
    }
  }

  componentDidMount() {
    Api.getPosts()
      .then(response => {
        const [error, data] = response
        if (error) {
          this.setState({
            isLoaded: true,
            posts: [],
            error: data
          })
        } else {
          this.setState({
            isLoaded: true,
            posts: data
          })
        }
      })
  }

  render() {
    const { error, isLoaded, posts } = this.state

    if (error) {

      return (
        <Alert color="danger">
          Error: {error}
        </Alert>
      )

    } else if (!isLoaded) {

      return (
        <Alert color="primary">
          Loading...
        </Alert>
      )

    } else {

      return (
        <Container>
          <Row>
            <Col>
              <PostsTable posts={posts}></PostsTable>
              <Link className="btn btn-primary" to="/posts/new">Add Post</Link>
            </Col>
          </Row>
        </Container>
      )

    }

  }
}

export default Posts

使用“新代码”,我的后端服务器发疯了: .... 在2019-05-26 20:45:39 +0200开始为127.0.0.1获取GET / api / posts 由Api :: PostsController#index处理为HTML   发布后加载(1.3ms)选择“帖子”。*从“帖子”   ↳app / controllers / api / posts_controller.rb:5 6ms内完成200 OK(查看:4.4ms | ActiveRecord:1.3ms) .... 等

5 个答案:

答案 0 :(得分:1)

我认为问题已经解决。谢谢。 实际上,我们同时面临两个问题。 1 /第二个参数中的[]是必要的, 2 /然后,正如@olivier所说,子组件中有一些错误代码:PostsTable。 “原始代码”:

class PostsTable extends Component {
  constructor(props) {
    super(props)
    this.state = {
      posts: props.posts
    }
  }

  render() {
    const posts = this.state.posts
    if (posts.length === 0) {
      return <div></div> ......

我这样做了: “新代码”:

class PostsTable extends Component {

  render() {
    const posts = this.props.posts
    if (posts.length === 0) {
      return <div></div> .....

我只用道具渲染PostsTable。 PostsTable现在是无状态的。 注意:在埃里克·伦敦(Eric London)的原始代码中,我认为PostsTable对象是在全局设置状态内创建的。 使用useEffect和setIsLoaded(true); setPosts(data)更加棘手,因为一旦启动setIsLoaded(true),就会创建PostsTable对象并进行渲染,而无需再次调用构造函数。

我结束了这样的重构:

export default function PostsTable(props)  {
  const posts = props.posts
  if (posts.length === 0) {
    return <div></div> ....

thx @olivier

答案 1 :(得分:0)

现在,您的`useEffect在每个渲染器上运行。您应该添加一个空数组作为第二个参数,以便仅在mount上运行:

useEffect(() => {
  ...
}, []);

通过提供一个数组作为第二个参数,您告诉useEffect函数“只要这些值之一发生更改,就再次运行”。如果根本不提供数组,它将在每次组件渲染时运行(在这种情况下,将调用setIsLoadingsetErrorsetPosts)。

为它提供一个空的依赖项数组仍会告诉该函数“这些值之一随时随地运行”,但是由于没有值会改变,因此只会运行一次。

答案 2 :(得分:0)

默认情况下,传递给使用Effect的函数将在每次渲染组件后执行。

当您在效果内部更新状态变量时,它将触发组件的重新渲染,然后效果将再次执行,基本上是无限循环。

如果将空数组传递给useEffect,则将只执行一次效果,因为只有在数组中的一个值发生更改时,效果才会重新执行。

以下代码可以解决问题

useEffect( () => {
  Api.getPosts()
    .then( response => {
      const [errata, data] = response
      if (errata) {
        setIsLoaded(true)
        setPosts([])
        setError(data)
      } else {
        setIsLoaded(true)
        setPosts(data)
      }
  })
}, [])

答案 3 :(得分:0)

useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setIsLoaded(true)
          setPosts([])
          setError(data)
          console.log('-+- showing data1 -+-')
          console.log(data)
        } else {
          setIsLoaded(true)
          setPosts(data)
          console.log('-+- showing data2 -+-')
          console.log(data)
        }
      })
  },[])

显示'-+-显示数据2-+- (5)[{…},{…},{…},{…},{…}] 0:{id:14,标题:“ qsdf”,正文:“ qsdf”,created_at:“ 2019-05- 26T13:07:58.006Z“,更新日期:” 2019-05-26T13:07:58.006Z“} 1:{id:15,标题:” qsdf“,正文:”鸡蛋“,created_at:” 2019-05-26T17 :47:48.323Z“,updated_at:” 2019-05-26T17:47:48.323Z“} 2:{id:16,标题:” qdf“,正文:” qdf“,created_at:” 2019-05-26T17: 48:52.257Z“,更新日期:” 2019-05-26T17:48:52.257Z“} 3:{id:17,标题:” qsdf“,正文:” h“,created_at:” 2019-05-26T19:49 :41.108Z“,更新日期:” 2019-05-26T19:49:41.108Z“} 4:{id:18,标题:” qsdf“,正文:” 123“,created_at:” 2019-05-26T19:59: 38.002Z“,更新日期:” 2019-05-26T19:59:38.002Z“}长度:5__proto __:Array(0)

答案 4 :(得分:-1)

很抱歉其他答案,但空数组不正确。您可以阅读Dan Abramov(Redux和React Hooks的创建者)撰写的长达49分钟的文章,这是为什么。

https://overreacted.io/a-complete-guide-to-useeffect/

我相信您可以像这样组合所有useState挂钩来解决问题:

export default function Posts() {
  const [state, setState] = useState({posts: [],
                                      isLoaded: false,
                                      error: null })


  useEffect( () => {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [],
                     error: data })
        } else {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [data],
                     error: null })
        }
      })
  }, [state.isLoaded] )

请确保您使用扩展语法传递了先前的状态,否则您的先前状态将不会保存。

替代方法在useEffect中使用条件,并将整个状态作为对useEffect的依赖项传递

export default function Posts() {
  const [state, setState] = useState({posts: [],
                                      isLoaded: false,
                                      error: null })


  useEffect( () => {
  if(!state.isLoaded) {
    Api.getPosts()
      .then( response => {
        const [errata, data] = response
        if (errata) {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [],
                     error: data })
        } else {
          setState({ ...state, 
                     isLoaded: true, 
                     posts: [data],
                     error: null })
        }
      })
    }
  }, [state] )