将问题从 class 重构为使用 hooks 函数

Refactoring problem from class to function with hooks

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

"Original code" 测试正常 "new code" 失败

//"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

"new code" 我的后端服务器快疯了: .... 在 2019-05-26 20:45:39 +0200 开始为 127.0.0.1 获取“/api/posts” Api::PostsController#index 处理为 HTML Post 加载(1.3 毫秒)SELECT "posts"。* 来自 "posts" ↳ 应用程序/控制器/api/posts_controller.rb:5 在 6 毫秒内完成 200 OK(视图:4.4 毫秒 | ActiveRecord:1.3 毫秒) .... 等等

现在,您的 `useEffect 在每个渲染器上 运行ning。您应该将一个空数组作为第二个参数添加到 only 运行 on mount:

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

通过提供一个数组作为第二个参数,您是在告诉 useEffect 函数 "Run again any time one of these values changes." 如果您根本不提供数组,它每次都会 运行组件呈现(在这种情况下,任何时候调用 setIsLoadingsetErrorsetPosts)。

给它一个空的依赖数组仍然告诉函数 "run anytime one of these values changes," 但因为没有值会改变,它只会 运行 一次。

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

当你在你的效果中更新一个状态变量时,它会触发你的组件的一个re-render,然后效果将在另一次执行,基本上你有一个无限循环。

如果您将一个空数组传递给 useEffect,您将只执行一次效果,因为只有当数组中的一个值发生变化时它才会 re-executed。

下面的代码应该可以解决问题

useEffect( () => {
  Api.getPosts()
    .then( response => {
      const [errata, data] = response
      if (errata) {
        setIsLoaded(true)
        setPosts([])
        setError(data)
      } else {
        setIsLoaded(true)
        setPosts(data)
      }
  })
}, [])
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, title: "qsdf", body: "qsdf" , created_at: "2019-05-26T13:07:58.006Z", updated_at: "2019-05-26T13:07:58.006Z"}1: {id: 15, title: "qsdf", body: "egg", created_at: "2019-05-26T17:47:48.323Z", updated_at: "2019-05-26T17:47: 48.323Z"}2: {id: 16, 标题: "qdf", body: "qdf", created_at: "2019-05-26T17:48:52.257Z", updated_at: "2019-05-26T17:48:52.257Z"}3: {id: 17, title: "qsdf", body: "h", created_at: "2019-05-26T19:49:41.108Z", updated_at: "2019-05-26T19:49:41.108Z"}4: {id: 18, title: "qsdf", body:“123”,created_at:“2019-05-26T19:59:38.002Z”,updated_at:“2019-05-26T19:59:38.002Z”}长度:5__proto__: 数组(0)

抱歉其他答案,但空数组不正确。您可以阅读 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] )

我想问题已经解决了。谢谢大家。 事实上,我们同时面临两个问题。 1/ [] 在第二个参数中是必要的, 2/ 然后正如@olivier 所说,子组件中有一些错误代码:PostsTable。 "original code":

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> ......

我这样做了: "New code":

class PostsTable extends Component {

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

我只用道具渲染 PostsTable。 PostsTable 现在是无状态的。 注意:在 Eric London 的原始代码中,我认为 PostsTable 对象是 created 从全局集合状态中创建的。 使用 useEffect 和 setIsLoaded(true); setPosts(data) 更棘手,因为一旦启动 setIsLoaded(true),就会创建和渲染 PostsTable 对象,而无需再次调用构造函数。

我这样结束重构:

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

谢谢@olivier