18-blog-发布文章跳转至文章页面

195 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

点击tab栏按钮,进入创建文章/setting页面

image.png

 <input 
className='form-control form-control-lg'
type="text"
placeholder='文章标题'
value={title||""}
onChange={this.changeTitle}
/>

每一个输入框里都有监听事件,去调用props中的方法:

 changeTitle = (ev)=>{
        this.props.changeTitle('title',ev.target.value)
    }

增强行为:

    changeTitle:(key,value)=>dispatch(action.articleFieldUpdate(key,value)), 

action中派发指令:

export const articleFieldUpdate = (key,value)=>{
    return {type:constant.ARTICLE_UPDATE_FIELD,key,value}
}

reducer 中根据指令写case方法更改state:

 case constant.ARTICLE_UPDATE_FIELD: //同步字段
       const key  = action.key
       const value  = action.value
       return {...state,[key]:value}

再通过props数据回显拿到所有输入的数据,标题,内容...数据,一并进行数据表格提交:

 <button
className='btn btn-primary pull-xs-right'     type='button'
onClick={this.onSubmitForm({title,description,body,tags})}>
   发布文章
</button>

点击按钮,触发props中事件:

 onSubmitForm = (article)=>(ev)=>{
    ev.preventDefault()
    this.props.createArticle(article)
  }
 createArticle:(article)=>dispatch(action.createArticle(article)),

将数据传递后,开始进行网络请求的书写,由于是提交数据,所以用到post方法,将获取到的数据对象传入body中让后台作为数据解析:

    create:(article)=>apiClient.post("/articles",{article}), //创建

来到action中利用网络请求获取数据并派发指令:

export const createArticle = (article)=>{
    return async (dispatch,getState)=>{
        try {
           const result = await  request.article.create(article)
            if(result.status===1){ // 成功 status 1
                const {slug} = result.data
                dispatch(push(`/articles/${slug}`)) //触发跳转 //router + redux
            }else{// 失败 status 0
                dispatch({type:constant.ARTICLE_CREATE_RESULT,result})  //result  : {status:0,message:result.message,errors:result.errors}
            }
        } catch (error) { // 错误
            console.log(error);
            dispatch({type:constant.USER_REGIST_RESULT,result:{message:error.message,errors:error.errors}})
        } 
    }
}

点击发布后,获取到数据的同时跳转至文章的页面:Push:"/article/:slug"页面

所以要再次封装一个文章内容的组件,在路由为/article/:slug时显示文章的所有内容(利用get请求)

//引入组件
const Article = lazy(() => import('./pages/Article'))
//路由出口
<Route path="/articles/:slug" component={Article} />

Article组件中留有组件准备回显 刚才创建文章时输入的信息,我们仍然使用数据增强的方式mapStateToProps的方式,将仓库中的数据映射至props中:

const mapStateToProps = state => ({
    article:state.article, //获取文章信息
    ...state.user //获取用户个人信息
})

就可以通过props接收到啦!

 const {article,currentUser,} = this.props //用户信息
 const { slug, title, description, body, tags, author, createdAt } = article  //文章数据

将各种信息放在value中进行回显:

注意,这里小细节是点击用户名称可以直接跳转到用户首页哦。

//比如title和avatar
 <h1>{title}</h1>
 <Link to={`/profile/${author && author.username}`}>
<img src={author && author.avatar} alt={author && author.username} />
</Link>

回到文章详情页中,还需要判断文章主体是否为该登陆用户本人发布,如果是本人发布,则可以设置编辑/删除功能,如果不是本人发布,则设置添加喜欢/取消喜欢的功能。

所以要将user信息传入至文章主体,我将这小部分按钮抽离出来单独组件再放在该文章大体中。

一共分为三种情况:

是登录用户本人自己的文章:编辑/删除

非该登录用户的文章:添加喜欢/取消喜欢,且显示喜欢该文章的总人数

用户根本没登陆:展示喜欢

所以制作一个if else语句:

 if(currentUser){ //登录
        const isOwn = currentUser&& author && currentUser.username === author.username
        if (isOwn) {
            return (
                <span>
                    <Link to={`/article/edit/${slug}`}>
                        <i className="ion-edit"></i> 编辑
                    </Link>
                    {" "}
                    <button
                        className="btn  btn-outline-danger btn-sm"
                        onClick={() => {
                            props.deleteArticle(slug)
                         }}
                    >
                        <i className="ion-trash-a"></i> 删除
                    </button>
                </span>
            )
        } else { // 不是自己的
            return (
                <button
                    className={favorited?FAVORITED_CLASS:NOT_FAVORITED_CLASS}
                    onClick={() => { 
                        if(favorited){
                            props.unfavoriteArticle(slug)
                        }else{
                            props.favoriteArticle(slug)
                        }
                    }}
                >
                    <i className="fa fa-heart-o"></i> {favoritedCount}
                </button>
            )
        }
    }else{ //未登录
        return (
            <button
                className="btn  btn-outline-danger btn-sm"
            >
                <i className="ion-trash-a"></i> 喜欢
            </button>
        )
    }

每一次都会触发网络请求,所以都要到action中做好请求并传递数据至reducer中。

并根据action派发的指令去reducer中改变想要改变的状态。

都是一样的步骤,不再去赘述。

此外还有在文章展示完的最底部,由评论板块,将会展示每一位用户的评论,我也将它抽离出单独组件方便书写:

逻辑判断:该用户没登陆,则不允许评论,即点击评论按钮跳转的是登录注册页面;用户登录的话那可以输入文字去评论。

 <CommentContainer 
                    currentUser={currentUser} //将用户信息通过props传入评论组件
                    slug={slug}
                />

在评论组件中接收用户信息,如果没有则显示登陆注册:

 if(!currentUser){  // 未登录
            return (
                <div className="col-xs-12 col-md-8 offset-md-2">
                    {/* 登录和注册列表 */}
                    <p>
                        <Link to="/login">登录</Link>
                        &nbsp; or &nbsp;
                        <Link  to="/regist">注册</Link>
                    </p>
                    {/* 评论列表 */}
                    <CommentList comments={this.props.comments} />
                </div>
            )
        }

如果登录,那可以点击添加评论并提交:

 <textarea className="form-control" placeholder="添加评论..." rows={3} onChange={this.handleChange}value={body}/>
<button className="btn btn-sm btn-primary"  type="submit">提交</button>

输入字段时要进行数据同步,这个是老生常谈了。直接说创建评论时候发送的网络请求:

要获取到具体在哪一篇文章下的哪一条评论,所以将文章唯一标识slug与评论本身的内容传进网络请求中

createComment = (e)=>{
        e.preventDefault()
        const slug  = this.props.slug //文章slug
        const body  = this.props.body //评论内容
        this.props.createComment(slug,body)
    }

调用action:

createComment:(slug,body)=>dispatch(action.createComment(slug,body)),

发送post网络请求创建评论:

 create:(slug,body)=>apiClient.post(`/comments/${slug}`,{body}), //创建评论post
export const createComment = (slug,body)=>{
    return async (dispatch,getState)=>{
        try {
           const result = await  request.comment.create(slug,body) //调用请求方法
           dispatch({type:constant.COMMENT_CREATE_RESULT,result})  //result  : {status:0,message:result.message,errors:result.errors}
        } catch (error) { // 错误
            console.log(error);
            dispatch({type:constant.COMMENT_CREATE_RESULT,result:{message:error.message,errors:error.errors}})
        }
    }
}

改变仓库中评论数据:

如果成功则,将该条评论通过concat方式加入至原有comment数组中;如果失败,则直接展示错误数据。

  case constant.COMMENT_CREATE_RESULT:  //创建评论结果
            if(action.result.status===1){
                const comment = action.result.data
                return {...state,body:'',comments:state.comments.concat([comment])}
            }else{
                return {...state,body:'',errors:{message:action.result.message}}
            }

评论展示:次数用到map遍历的方法,遍历出每一条评论(没有就显示“空”):

 <div>
                {
                   props.comments.map(comment=>{
                        return <CommentItem key={comment.id} 
                                            comment={comment} 
                                            currentUser={currentUser} 
                                            deleteComment={deleteComment}
                                            slug={props.slug}
                       />
                   }) 
                }
            </div>

具体每一条评论,就是接收到评论内容,评论者本人进行信息展示。

关于初始展示所有评论内容,只需要使用一个get方法,获取到该文章slug下的所有评论然后展示出来即可。

这个初始展示所有评论的时机是要在评论页面挂载之后就执行的,也就是生命周期钩子函数componentDidMount()

 componentDidMount(){
     this.props.initComments(this.props.slug)
 }
 ---------------------------------------------------------
 initComments:(slug)=>dispatch(action.initComments(slug)),
 

在action中派发指令:

action中使用get方法进行数据请求:
export const initComments = (slug)=>{
    return async (dispatch,getState)=>{
        try {
           const result = await  request.comment.get(slug)
           console.log(result)
           dispatch({type:constant.COMMENTS_GET_RESULT,result})  //如果出错result  : {status:0,message:result.message,errors:result.errors}
        } catch (error) { // 错误
            console.log(error);
            dispatch({type:constant.COMMENTS_GET_RESULT,result:{message:error.message,errors:error.errors}})
        }
    }
}

至此完成输入文章数据并点击按钮发布成功后立即跳转至文章详情页面,显示作者头像(点击可进入主页),显示用户名,显示所有评论;并可以判断是否为文章本人,如果不是文章本人则可以选择喜欢/取消喜欢文章,如果是文章作者本人,那可以选择编辑/删除该文章;同时对是否登录的用户也可以进行评论限制,倘若该用户登录,那可以点击输入并发表评论,如果没有登录,那点击输入框之后就跳转至登录/注册的组件。