将问题从 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." 如果您根本不提供数组,它每次都会 运行组件呈现(在这种情况下,任何时候调用 setIsLoading
、setError
或 setPosts
)。
给它一个空的依赖数组仍然告诉函数 "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
我正在重构一些代码,但现在卡住了。 原始代码来自 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." 如果您根本不提供数组,它每次都会 运行组件呈现(在这种情况下,任何时候调用 setIsLoading
、setError
或 setPosts
)。
给它一个空的依赖数组仍然告诉函数 "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