Как преобразовать код React-Redux на основе классов с помощью хуков

Я изучаю React / Redux, и я пытаюсь реорганизовать этот код с класса на функциональный код / ​​код на основе хуков. Приложение - это упражнение, над которым я работаю, оно состоит из трех компонентов - Posts.js, из которых я получаю список сообщений с сайта typicode.com. У каждого сообщения из полученного списка есть атакованная кнопка. В onClick должна отображаться подробная информация о каждом посте (PostDetails.js и Comments.js):

Концепция

На данный момент и сообщения, и комментарии являются компонентами на основе классов. Мне необходимо:

Шаг 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
})

компоненты / 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);

компоненты / 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);

компоненты / 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);

person Monika    schedule 22.02.2021    source источник
comment
Если вы конвертируете это в функциональные компоненты и перехватчики, вы можете использовать перехватчики useSelector и useDispatch и избавиться от connect. Кроме того, вы можете управлять состоянием вашего компонента с помощью ловушки useState.   -  person Ajeet Shah    schedule 22.02.2021
comment
Я знаю, но не знаю, как их реализовать. У меня конфликт между использованием Redux и useState для управления состоянием. @AjeetShah   -  person Monika    schedule 22.02.2021


Ответы (1)


Это может быть способ создать Posts компонент:

Я предполагаю, что когда вы отправляете fetchPosts() действие, вы сохраняете его ответ с помощью редукторов в Redux.

И вам не нужно 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
person Ajeet Shah    schedule 22.02.2021
comment
Уххх, отлично, это решило проблему с бесконечным циклом, которая у меня была с useEffect. Я также предполагал, что setState не понадобится, однако я не был точно уверен, поэтому спасибо за разъяснение! Перехожу к решению следующей составляющей! Большое спасибо! - person Monika; 22.02.2021
comment
Я не понимаю Аджита. Я новичок в StackOverflow. Должен ли я что-то сделать, чтобы вы проголосовали за помощь мне? Будет приятно, дружище :) Просто дай мне знать, как - person Monika; 22.02.2021