如何使用 Hooks 转换基于 class 的 React-Redux 代码

How to transform class-based React-Redux code using Hooks

我正在学习 React/Redux,我正在尝试将此代码从基于 class 重构为 functional/hooks-based 代码。该应用程序是我正在做的一个练习,它有三个组件 Posts.js,我从 typicode.com 中获取 post 的列表。获取列表中的每个 post 都有一个按钮被攻击。 在 onClick 上,它应该显示每个 post(PostDetails.js 和 Comments.js)的详细信息:

目前,帖子和评论都是基于 class 的组件。我需要:

第 1 步:将它们更改为功能组件并使用 React Hooks,但仍保留 connect()、mapStateToProps 和 mapDispatchToProps;

第 2 步:实施 React-Redux 挂钩(UseSelector、useDispatch)

App.js

//imports...
const App = () => {
    return (
        <div className="container">
            <div><Posts /></div>
            <div><PostDetails /></div>
        </div>
    )
}

export default App;

动作

import jsonPlaceholder from '../apis/jsonPlaceholder';

export const fetchPosts = () => async dispatch => {
    const response = await jsonPlaceholder.get('/posts');
    dispatch({type: 'FETCH_POSTS', payload: response.data})
};


export const selectPost = post => {
    return ({
        type: 'POST_SELECTED',
        payload: post
    })
}


export const fetchComments = (id) => async dispatch => {
    const response = await jsonPlaceholder.get(`/comments?postId=${id}`);
    dispatch({type: 'FETCH_COMMENTS', payload: response.data})
}

减速机

export default (state = [], action) => {
    switch (action.type) {
        case 'FETCH_POSTS':
            return action.payload;
        default:
            return state;
    }
}

export default (selectedPost = null, action) => {
    if (action.type === 'POST_SELECTED') {
        return action.payload;
    }
    return selectedPost;
}

export default (state = [], action) => {
    switch (action.type) {
        case 'FETCH_COMMENTS':
            return action.payload;
        default:
            return state;
    }
}

export default combineReducers({
    posts: postsReducer,
    selectedPost: selectedPostReducer,
    comments: commentsReducer
})

components/Posts.js

import React from 'react';
import { connect } from 'react-redux';
import { fetchPosts, selectPost } from '../actions';
import '../styles/posts.scss';


class Posts extends React.Component {
    componentDidMount() {
        this.props.fetchPosts()
    }

    renderPosts() {
        return this.props.posts.map(post => {
            if (post.id <= 10)              
            return (
                <div className='item' key={post.id}>
                    <div className="title">
                        <h4>{post.title}</h4>
                    </div>
                    <button
                        onClick={() => {
                            this.props.selectPost(post)
                            console.log(post)
                        }
                    }>Open</button>
                    <hr/>
                </div>
                )
         })
    }

    render() {
        return(
            <div className="list">
                { this.renderPosts() }
            </div>
        )
  }
    
}

const mapStateToProps = state => {
    return {
        posts: state.posts,
        selectedPost: state.post
    }
};

const mapDispatchToProps = {
    fetchPosts,
    selectPost
}

export default connect(mapStateToProps, mapDispatchToProps)(Posts);

components/PostDetails.js

import React from 'react';
import { connect } from 'react-redux';
import Comments from './Comments'

const PostDetails = ({ post }) => {
    if (!post) {
        return <div>Select a post</div>
    }
    return (
        <div className="post-details">
            <div className="post-content">
                <h3>{post.title}</h3>
                <p>{post.body}</p>
                <hr/>
            </div>
            <div className="comments-detail">
                <Comments postId={post.id}/>
            </div>
        </div>
    )
}

const mapStateToProps = state => {
    return {post: state.selectedPost}
}


export default connect(mapStateToProps)(PostDetails);

components/Comments.js

import React from 'react';
import { connect } from 'react-redux';
import { fetchComments } from '../actions'

class Comments extends React.Component {
    componentDidUpdate(prevProps) {
        if (this.props.postId && this.props.postId !== prevProps.postId){
            this.props.fetchComments(this.props.postId)
        }
    }

    renderComments() {
        console.log(this.props.comments)
        return this.props.comments.map(comment => {
            return (
                <div className="comment" key={comment.id}>
                    <div className="content">
                        <h5>{comment.name}</h5>
                        <p>{comment.body}</p>
                    </div>
                    <hr />
                </div>
            )
        })

    }

    render() {
        return (
            <div className="comments">
                {this.renderComments()}
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {comments: state.comments}
}

export default connect(mapStateToProps, {fetchComments})(Comments);

这可能是一种创建 Posts 组件的方法:

我假设当您 dispatch fetchPosts() 操作时,您正在使用 Redux 中的 reducers 保存其响应。

而且,您不需要 fetchedPosts 处于本地组件状态,因为您已经在 Redux 状态中拥有此数据。

const Posts = () => {
  const posts = useSelector((state) => state.posts)
  const dispatch = useDispatch()
  // const [fetchedPosts, setFetchedPosts] = useState([]) // NOT needed

  useEffect(() => {
    dispatch(fetchPosts())
    // setFetchedPosts(posts) // NOT needed
    // console.log(posts) // NOT needed, its value may confuse you
  }, [])

  // Do this, if you want to see `posts` in browser log
  useEffect(() => {
    console.log(posts)
  }, [posts])

  /* NOT needed
  const renderPosts = () => {
    posts.map((post) => {
      console.log(post)
    })
  } */

  return (
    <>
      {posts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </>
  )
}

export default Posts