xingbofeng.github.io icon indicating copy to clipboard operation
xingbofeng.github.io copied to clipboard

网易有道智能题库项目总结

Open xingbofeng opened this issue 6 years ago • 0 comments

举一些简单的例子说说我写的不太周全的代码吧。

为何你要去多次dispatch?

参考以下这段我已经修改后代码

async action({ store, params }) {
  // 判断store里的id和当前id是否一致,若一致,则不请求后台
  console.log('chapter')
  const chapterInfos = store.getState().home.chapterInfos;
  if (Object.keys(chapterInfos).length === 0 ||
    chapterInfos.subject.id !== parseInt(params.chapter, 10)) {
    await store.dispatch(chapter(params.chapter));
  }
}

这里的重点是通过dispatch一个action,改变store里面的状态。

或许你就会像我一样,少了一个判断吧?

其实不管是否去请求接口,dispatch之前and之后,若是同样的状态,做这个无谓的状态更新又有何意义呢?

你在await一个假Promise?

我在action写了这样一个方法,其中server.enterHome()是一个经过封装的fetch方法,用于请求接口。

export function home() {
  return (dispatch) => {
    server.enterHome()
      .then(res => res.json())
      .then(json => {
        dispatch(enterHome(json.data));
      })
      .catch(err => {
        console.log(err);
      });
  }
}

然后当时我们是这么调用的:

async action({ store }) {
  if (Object.keys(store.getState().home.homeData).length === 0) {
    await store.dispatch(home());
  }
}

后来发现原来是await语句,会在第一次then返回时就结束了,开始执行后面的同步代码。因而可以说这个await是假的。

我们进行了如下修改:

export function home() {
  return async function (dispatch) {
    const resp = await server.enterHome()
    const { data } = await resp.json()
    dispatch(enterHome(data));
  }
}

我曾一度想引入lodash进行对象判等?甚至还想用递归……

以下是一段测试数据:

情况就是每一章的children对应的是每一节的内容,每一节有唯一的一个id进行标识。

我使用一个状态currentSection记录当前用户所在节的信息:

然而每次作dispatch都要去对当前currentSection和改变后的currentSection进行判等。众所周知,对象判等相当消耗性能。所以探讨后是这样的解决办法:为何不把currentSection用一个对象保存下来,使用唯一id作为键值标识,不用对象判断,而判断唯一id作为标识的对象是否存在呢?

因此我改为下面这样:

// action.js

// 当前节详情信息
export const CURRENT_SECTION = 'CURRENT_SECTION';

export const changeSection = makeActionCreator(CURRENT_SECTION, 'currentSection');

export function changeCurrentSection(data) {
  return (dispatch, getState) => {
    // 进行判断,如果store 里已经存了当前节信息那就不dispatch
    const { home: { currentSection } } = getState();
    if (!Object.keys(currentSection).includes(`${data.infosId}`)) {
      dispatch(changeSection(data));
    }
  }
}
// reducer.js
case CURRENT_SECTION:
  return Object.assign({}, state, {
    currentSection: {
      ...state.currentSection,
      [action.currentSection.infosId]: {
        ...action.currentSection,
      },
    },
  })

其实都根本不用currentSection,遍历一次就好了

componentWillMount() {   
 const {    
   currentSection,    
   chapterInfos,    
   params,    
 } = this.props;    
 if (Object.keys(currentSection).length !== 0) {    
   this.setState({    
     currentSection: currentSection[params.section],    
   });    
 } else {   
   chapterInfos.subject.children.forEach(value => {   
     if (`${value.id}` === `${params.section}`) {   
       this.setState({    
         currentSection: {    
           chapterId: parseInt(params.chapter, 10),   
           children: value.children,    
           courseTitle: value.name,   
         },   
       });    
     }    
   });    
 }    
}

我甚至为了fix直接进入页面没数据的bug,在componentWillMount()里写了这样一段丑陋的代码。

然而根本就不需要currentSection,毕竟它也是从同一个接口取出来的。只需要在render()里遍历一遍chapterInfos就好了啊……

let data = null;
chapterInfos.subject.children.forEach((secData) => {
  if (secData.id === parseInt(params.section, 10)) {
    data = secData;
  }
});

只能感叹,还需修行……

反思的时候我仿佛觉得自己写了一坨屎

这是我一个组件的代码:

import React, { PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import CourseCard from '../../components/p_home/CourseCard';
import * as actions from '../../actions/home';

class HomePage extends React.Component {
  static propTypes = {
    homeData: PropTypes.object.isRequired,
    changeCurrentSection: PropTypes.func.isRequired,
  };
  componentDidMount() {
    if (window.ydk !== undefined) window.ydk.hideLoading() // 隐藏加载中按钮
  }
  render() {
    const { homeData, changeCurrentSection } = this.props;
    if (Object.keys(homeData).length === 0) {
      return null
    }

    return (
      <CourseCard
        homeData={homeData}
        changeCurrentSection={changeCurrentSection}
      />);
  }
}

function mapStateToProps(state) {
  return {
    homeData: state.home.homeData,
  };
}
function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch);
}

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

乍一看啥也没错,但是render()里面啥也没有直接返回一个组件……那为何不直接把这个组件拉出来呢……

import React, { PropTypes } from 'react';
import s from './style.css';
import Link from '../../Common/Link';

class CourseCard extends React.Component {
  static propTypes = {
    homeData: PropTypes.object.isRequired,
    changeCurrentSection: PropTypes.func.isRequired,
  };

  render() {
    // latest是最近做过的题目,subjects是章节内容
    const { homeData, changeCurrentSection } = this.props;
    const subjects = homeData.subjects || [];
    const latest = homeData.latest || [];
    return (
      <div className={s.container}>
        <header>智能题库</header>
        <main>
          {latest.length === 0 ?
            null :
            <div className={s.itemsBox}>
              <h1>—— 最近练习 ——</h1>
              {latest.map((value, index) =>
                <div className={s.cardsItem} key={index}>
                  <Link
                    to={`/home/${value.chapterId}/${value.subject.id}`}
                    onClick={() => changeCurrentSection({
                      children: value.subject.children,
                      chapterId: value.chapterId,
                      courseTitle: value.subject.name,
                    })}
                  >
                    {value.subject.name}
                  </Link>
                </div>)}
            </div>
          }
          {/* 这里要做两次循环,一次遍历每类考试名,遍历后的结果再遍历每门课 */}
          {subjects.length === 0 ?
            null :
            subjects.map((value, index) =>
              <div className={s.itemsBox} key={index}>
                <h1>{value.category}</h1>
                {value.subjects.map((v, i) =>
                  <div className={s.cardsItem} key={i}>
                    <Link
                      to={`/home/${v.id}`}
                    >
                      {v.name}
                    </Link>
                  </div>,
                )}
              </div>,
            )
          }
        </main>
      </div>
    );
  }
}

这里做了多次遍历,却没有提取出来组件。还是感觉图样图森破啊……

因此后面是这么改的

render() {
  const { homeData } = this.props;
  const subjects = homeData.subjects || [];
  const latest = homeData.latest || [];
  return (
    <div className={s.container}>
      {latest.length === 0 ? null :
      <div className={s.latestItemsBox}>
        <div className={s.title}>最近练习</div>
        <div className={s.itemCont}>
          {latest.map((value, index) =>
            <LatestExercise
              value={value}
              key={`latest_card_${index}`}
              linkClick={this.linkClick}
            />)}
        </div>
      </div>}
      {subjects.length === 0 ? null :
        subjects.map((value, index) =>
          <CourseCard
            key={`course_card_${index}`}
            value={value}
            linkClick={this.linkClick}
          />,
      )}
    </div>
  );
}

希望以后写的代码越来越健壮吧。虽然我是真的不适合编程。哈哈哈哈。

xingbofeng avatar Jul 23 '17 12:07 xingbofeng