Dace

Dace

  • 文档
  • API
  • 帮助
  • 博客

›教程

教程

  • 入门
  • 页面之间的导航
  • 使用公共组件
  • 创建动态页面
  • 创建简洁明了的网页地址
  • 获取页面数据
  • 与 Redux 共舞
  • 部署网站

概念

  • 命令行工具
  • 路由规则
  • 环境变量
  • 请求代理
  • 配置文件
  • 插件

常见问题

  • 自定义 eslint 规则
  • 自定义 stylelint 规则
  • 自定义 babel 规则
  • 自定义 postcss 规则
  • 自定义 webpack 配置
  • 自定义服务器端渲染模版
  • 自定义网页路由
  • 拆分打包
  • 将静态文件发布到 CDN

插件

  • Dace-plugin-redux

与 Redux 共舞

得益于 Dace 路由API的优点,我们知道了如何创建一个具有简洁 URL 的 Dace 应用程序。

实际上,我们通常需要从远程数据源获取数据。Dace 提供了一个标准 API 用于为页面获取数据。我们使用一个 async 静态方法 getInitialProps 来达到获取数据的目的。

以此为基础,我们能够给以页面从远程数据源获取数据,然后把数据传给我们的一个页面组件的属性。我们可以编写 getInitialProps 函数让他能够同时在客户端和服务器端运行。

在这节课中,我们要实现的功能是:在首页获取用户列表,点击用户名,可以查看用户的详细信息。

现在开始!

设置

下载需要的示例程序:

git clone https://github.com/dacejs/learn-dace-demo.git
cd learn-dace-demo
git checkout 06.fetching-data-for-pages

用下面的命令运行:

npm install
npm start

然后,访问 http://localhost:3000

上一节课,我们学会了从服务器端获取数据并渲染页面,这对于很多简单的应用已经够用了。不过,当应用变得越来越复杂时,数据状态管理也会更复杂,业内已经有很多状态管理工具,其中 redux 是普及最广泛的一种。今天我们就来学习 dace + redux 开发复杂应用。

安装 dace-plugin-redux

首先,我们安装 dace-plugin-redux 插件,它会包含 redux 相关的依赖以及修改后的 dace 核心代码。

npm i dace-plugin-redux

增加 dace 配置文件

在工程根目录增加 dace 配置文件 dace.config.js,内容如下:

const { reduxConfig } = require('dace-plugin-redux');

module.exports = reduxConfig;

这个文件的目的是修改 dace 默认 webpack 配置。

OK,前期准备工作就绪,我们开始进入正题——获取文章列表。

获取文章列表

在我们的演示程序中,显示了一个博客列表,现在我们用 dace + redux 改造演示程序。

通常一个 redux 应用会包含:

  • action.js
  • reducer.js

建议为每一个页面建一个目录,这样组织文件结构会很清晰。

新建 src/pages/index 目录,并在该目录分别创建 index.jsx, action.js, reducer.js:

src/pages/index/action.js 内容如下:

export const FETCH_POSTS = 'fetch_posts';
export const fetchPosts = () => async (dispatch, getState, api) => {
  const { posts } = getState();
  if (!posts) {
    const res = await api.get('http://jsonplaceholder.typicode.com/posts');
    return dispatch({
      type: FETCH_POSTS,
      payload: res
    });
  }
  return null;
};

src/pages/index/reducer.js 内容如下:

import { FETCH_POSTS } from './action';

export default (state = {}, action) => {
  switch (action.type) {
    case FETCH_POSTS:
      // 只能返回对象,不能返回数组
      return {
        ...state,
        posts: action.payload.data.map(({ id, title }) => ({ id, title }))
      };
    default:
      return state;
  }
};

注意到了吗,action.js 、reducer.js 这 2 个文件就是标准的 redux 用法,没有什么特殊代码。

然后,src/pages/index/index.jsx 文件会很一些不同:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'dace';
import { fetchPosts } from './action';
import reducer from './reducer';
import Layout from '../../layouts/default';

@connect(state => state)
export default class Index extends Component {
  static propTypes = {
    posts: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.number,
      title: PropTypes.string
    }))
  };

  static defaultProps = {
    posts: []
  }

  static getInitialProps = (ctx) => {
    ctx.store.injectReducer(reducer);
    return ctx.store.dispatch(fetchPosts());
  }

  componentDidMount() {
    this.props.store.injectReducer(reducer);
  }

  render() {
    return (
      <Layout>
        <h1>Post List</h1>
        <ol>
          {
            this.props.posts.map(post => (
              <li key={post.id}>
                <Link to={`/post/${post.id}`}>{post.title}</Link>
              </li>
            ))
          }
        </ol>
      </Layout>
    );
  }
}

上面的代码一切看来都是很熟悉了,getInitialProps 包含的代码和之前有些不同:

static getInitialProps = (ctx) => {
  ctx.store.injectReducer(reducer);
  return ctx.store.dispatch(fetchPosts());
}

getInitialProps 做了 2 件事:

  • 在全局 store 中注入了当前页面需要的 reducer。
  • 触发 action 方法获取数据。

需要注意的是,当执行服务器端渲染时, getInitialProps 修改的是服务器端的 store ,即 reducer 注入到了服务器端的 store ,这就导致了问题——当在同一页面中浏览器端代码触发 action 时,找不到对应的 reducer 来更新 state ,解决的方法是,在浏览器执行的代码(componentDidMount())中再执行一次 injectReducer 方法。

componentDidMount() {
  this.props.store.injectReducer(reducer);
}

实现文章展示页面

展示页面和列表页面基本上差不多,直接上代码。

src/pages/post/action.js 内容如下:

export const FETCH_POST = 'fetch_post';
export const fetchPost = id => async (dispatch, getState, api) => {
  const res = await api.get(`http://jsonplaceholder.typicode.com/posts/${id}`);
  return dispatch({
    type: FETCH_POST,
    payload: res
  });
};

src/pages/post/reducer.js 内容如下:

import { FETCH_POST } from './action';

export default (state = {}, action) => {
  switch (action.type) {
    case FETCH_POST:
      // 只能返回对象,不能返回数组
      return {
        ...state,
        post: action.payload.data
      };
    default:
      return state;
  }
};

src/pages/post/index.jsx 内容如下:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchPost } from './action';
import reducer from './reducer';
import Layout from '../../layouts/default';

@connect(state => state)
export default class Post extends Component {
  static propTypes = {
    post: PropTypes.object
  };

  static defaultProps = {
    post: {}
  }

  static getInitialProps = (ctx) => {
    ctx.store.injectReducer(reducer);
    return ctx.store.dispatch(fetchPost(ctx.match.params.id));
  }

  componentDidMount() {
    this.props.store.injectReducer(reducer);
  }

  render() {
    const { post } = this.props;
    return (
      <Layout>
        <h1>{post.title}</h1>
        <p>{post.body}</p>
      </Layout>
    );
  }
}

这里使用了 getInitialProps 的 ctx 参数,查看 getInitialProps 文档

使用装饰器简化编程

上面虽然实现了 redux 对数据的管理,但每个页面组件中需要写两次 injectReducer,另外 componentDidMount() 的代码完全一样,有没有办法让我们的代码变得更优雅呢? dace-plugin-redux 提供了一个 @getInitialProps() 装饰器,用这个装饰器能让代码看起更简洁。

我们用 @getInitialProps() 装饰器来修改 src/pages/index/index.jsx:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'dace';
import { getInitialProps } from 'dace-plugin-redux';
import { fetchPosts } from './action';
import reducer from './reducer';
import Layout from '../../layouts/default';

@getInitialProps({
  reducer,
  promise: ({ store }) => store.dispatch(fetchPosts())
})
@connect(state => state)
export default class Index extends Component {
  static propTypes = {
    posts: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.number,
      title: PropTypes.string
    }))
  };

  static defaultProps = {
    posts: []
  }

  render() {
    return (
      <Layout>
        <h1>Post List</h1>
        <ol>
          {
            this.props.posts.map(post => (
              <li key={post.id}>
                <Link to={`/post/${post.id}`}>{post.title}</Link>
              </li>
            ))
          }
        </ol>
      </Layout>
    );
  }
}
  • 从 dace-plugin-redux 中导出 getInitialProps
  • 对页面组件使用 getInitialProps 装饰器,装饰器接收 2 个参数:
    • reducer: 当前页面使用的 reducer。
    • promise: 当前页面初始化执行的函数,即 getInitialProps 静态方法中的代码。
  • 将页面组件的 getInitialProps 静态方法 和 componentDidMount 方法的代码都删除。

最后

现在你学到了 Dace + Redux 开发。

Last updated on 2018-12-14
← 获取页面数据部署网站 →
  • 设置
  • 安装 dace-plugin-redux
  • 增加 dace 配置文件
  • 获取文章列表
  • 实现文章展示页面
  • 使用装饰器简化编程
  • 最后
Dace
文档
教程常见问题API
社区
Stack Overflow反馈
更多
博客GitHubStar
Dace
Copyright © 2018 dace