致虚极 守静笃
动态字段
2020-05-17发布 261

详情组件

之前我们编写了ArticleDetail这个组件,不过它目前只能显示article+ID这样一串文本,让我们修改代码,让这个组件显示实际的文章内容吧。

首先目前我们点击首页的文章标题,指向的url为articles/id/,这与我们后端的url重复了。为此,更改一下backend/urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    # 全都放到api这个路径后面
    path('api/', include('article.urls')),
    path('auth/', include('rest_framework_social_oauth2.urls')),
]

注意之前ArticleList组件中请求API的地方要做出修改哦

接着修改ArticleDetail.js

import React, {useEffect, useState} from "react";
import { useParams } from 'react-router-dom';

const ArticleDetail = () => {
  // HOOK
  const { articleId } = useParams();
  const [loading, setLoading] = useState(true);
  const [article, setArticle] = useState({});
  const fetchArticle = () => {
    fetch(`/api/articles/${articleId}/`)
      .then(response => response.json())
      .then(result => {
        setArticle(result);
        setLoading(false);
      })
      .catch(e => console.log(e));
  }
  useEffect(fetchArticle, [])

  return (
    loading
    ?
    <div>加载中...</div>
    :
    <div>
      <h4>{article.title}</h4>
      <p>{article.body}</p>
    </div>
  )
}

export default ArticleDetail

代码很烂,仅供参考。之前我们讲过类组件,只有类组件有生命周期,有state,但是现在React有了HOOK这一功能,看我们的代码中,以use开头的地方,都是HOOK

useState使函数组件可以像类组件一样使用state,它使用一个参数作为初始值,并返回一个数组,例如代码中第7行loading部分,我们给了名为loadingstate初始值false,返回的数组中第一个元素是loading这个state,而第二个则是一个函数,可以用于更改state值。

之后我们定义一个获取文章的函数,获取结果后设置article,并将loading状态设为falseuseEffect则类似类组件的componentDidMount这个生命周期函数,在组件挂载后,将会执行通过参数传递的回调函数,获取文章数据。第二个参数[]使得这个函数只在组件挂载后运行一次。

有关HOOK的详情可以去看React官方文档。

返回值部分的JSX就不说了,只是三元运算符根据loading状态做条件渲染,没有特别的地方。

动态字段

刚刚这个详情页面,其实主要是为了说明一种情况,对于后端定义的API,并不是在任何地方都需要全部数据的。例如,在首页我们只需要文章标题、发布时间,详情页才显示文章内容。尽管现在硬件条件越来越好,但如果把所有数据都通过网络传输到客户端,无疑是对网络资源的浪费

不过如果为前端不同页面定义不同的序列化器或者修改模型那就太麻烦了,我们可以通过定义请求中的参数如fields=xx,xxx来实现这一点,已经有一个好用的第三方库帮我们做到啦。那就是drf-flex-fields,让我们使用pip来安装:

$ pip install drf-flex-fields

要使用也非常简单,只需要修改article/serializers.py

from rest_flex_fields import FlexFieldsModelSerializer
from article.models import Article
from rest_framework import serializers


class ArticleSerializer(FlexFieldsModelSerializer):
    author = serializers.ReadOnlyField(source='author.username')

    class Meta:
        model = Article
        fields = ['id', 'author', 'title', 'body', 'created', 'updated']

用命令行测试一下:

$ http http://127.0.0.1:8000/api/articles/\?fields\=author,body
...
[
    {
        "author": "elliot",
        "body": "时间测试"
    },
    {
        "author": "elliot",
        "body": "现在是4月5日11点10分"
    }
]

 $ http http://127.0.0.1:8000/api/articles/\?omit\=author,body  
...

[
    {
        "created": "2020-04-05T11:47:49.087547+08:00",
        "id": 2,
        "title": "现在时间",
        "updated": "2020-04-05T11:47:49.087580+08:00"
    },
    {
        "created": "2020-04-05T11:10:56.880622+08:00",
        "id": 1,
        "title": "第一天",
        "updated": "2020-04-05T11:10:56.880674+08:00"
    }
]

既可以通过fields参数来指定需要的字段,也可以通过omit来指定要排除哪些字段。注意url中参数要用问号?开头,在命令行中则要用\?来转义问号。

这时候可以去修改前端代码:

......
const ArticleItem = props => (
  <div key={props.item.id}>
    ......
    <p>
      {/* 删掉这行<strong>{props.item.body}</strong>*/}
      ......
    </p>
  </div>
)

class ArticleList extends Component {
......
  componentDidMount() {
    // 修改这里
    fetch('/api/articles/?omit=author,body')
      .then(response => response.json())
      .then(result => this.setState({articleList: result}))
      .catch(e => this.setState({error: e}));
  }
......

}
......

效果图

现在首页就没有文章详细内容了。文章详情组件就留给读者自己去修改了。

drf-flex-fields这个库的功能可不止于此哦,建议读者去Github看看这个库的说明与源码,可以使我们的API便捷的实现关系型字段的联合查询。

最后

最近对这个系列的文章有些思考,在这里不打算一步一步做一个实际的,或者说好看的,优雅的博客系统,而是想到一些功能,尽量加上去,实践DjangoReact的结合,如果有读者希望我实现什么功能,可以在公众号留言,最终我们可能会做出一个很奇葩的应用,哈哈。如果想要直接复制代码做一个个人博客,可以参考我的个人博客,Github地址:https://github.com/Eliot00/elliot00.com