前端设计文档

主要负责人:邱奕浩 邵梓硕

审核人:邱晓裕,蒲其刚

一、 技术选型及理由

本项目的前端选用的技术和使用理由如下:

二、 架构设计

项目的架构设计如下图所示,该项目使用经典的CS架构,使用Nginx进行反向代理,提供网页文件服务,并使用多个dockers运行后台程序,docker与数据库之间进行数据交互,可以很方便提高服务器的抗压能力、性能。

三、 模块划分

本项目的前端模块划分如下:

src: 
 |--- assets       // 素材文件夹
 |--- components   // 低层组件文件夹
 |--- views        // 子界面文件夹
 |--- layouts      // 主界面文件夹
 |--- variables    // 常用的全局常量和函数
 |--- route.js     // 定义项目的基本路由
 |--- index.js     // 项目入口文件

这里简单介绍一下组件的划分,组件的架构大致如下图所示:

四、软件设计技术

1. 面向对象编程

在ES6语法中,React组件开发常常使用类的形式来组织组件,以面向对象的方式来定义组件的内容和行为。

在layout,view目录下都有组件以继承React.Component的类表示:

view:
 |--- UserProfile		//个人信息界面
 |--- LoginPage			//登录界面
 |--- RegisterPage		//注册界面
 |--- DashBoard			//DashBoard界面

layout:
 |--- Admin				//主界面跳转

类组件主要重写render函数,返回需要在前端渲染出的html内容,根据实际要求其他组件生命周期函数。

在类组件中state被识别为类的私有变量,在react组件的渲染,更新和扩展方面带来了很大的便利,以LoginPage为例:

初始化

state在构造函数中初始化,我们将它视为这个页面的逻辑中所需要的一些数据或者状态量即可:

constructor(props) {
    super(props);

    this.state = {
      userphone: "",		//数据
      password: "",			//数据
      submitted: false,		//状态量
      responseMsg: " ",		//数据
      user_error: false,	//状态量
      password_error: false	//状态量
    };
    
    //...
}
实时渲染

类组件将state中的数据放进渲染到前端的html中去,可以实时显示数据在前端。

首先在render中拿出state中的数据,格式为{数据1名,数据2名,...} = this.state:

render() {
    const {
      userphone,
      password,
      submitted,
      responseMsg,
      user_error,
      password_error
    } = this.state;	//可以拿到state中的所有数据和状态量
    return (
    	//...
    );
}

其次在组件中利用变量名即可:

<TextField
    ...
    value={userphone}				//将state中userphone显示在输入区域
    ...
    />

也可以进行逻辑判断:

{submitted && !userphone && (		//使用状态量进行逻辑判断
    <div className="help-block" style=>
        手机号不可为空
    </div>
)}
修改

类组件对变量的修改使用this.setState(),参数必须是和state相同的目录结构,比如提交表单后修改状态量:

handleSubmit(e) {
    e.preventDefault();
    this.setState({ submitted: true });			//修改state中的submitted为true
    //...
}
可扩展性

由于数据变量使用的便利和类的特性,类组件拥有良好的可扩展性,比如可以在类中加入自定义方法,方便对组件逻辑进行模块化管理:

//文件位置:src/views/LoginPage/LoginPage.jsx
class LoginPage extends React.Component{
    
    handleChange(e) {}
    handleSubmit(e) {}
    login(userphone, password) {}
}

或者将数据进行结构化存入state中:

//文件位置:src/views/UserProfile/UserProfile.jsx
this.state = {
    ...
    user: {
        name: null,
        phone: null,
        password: null,
        ...
    },
    ...
};

因此对组件进行渲染和功能上的扩展都比较方便。

2. 函数式组件编程

使用函数来开发组件已经渐渐地成为了越来越多React开发者的选择。得益于 Hooks 功能,函数组件也能实现近似类组件一样的生命周期功能和React状态控制,使用Hooks已经渐渐地成为了一种编程范式。我们搭配Hooks功能,使用函数式组件开发,能够有效降低组件的状态复杂度,便于组件的解耦和组织。

在本次项目,同样也使用了函数式组件的开发理念,搭配常用的Hooks函数如useState,useEffect,帮助我们快速开发应用。

以下给出几个使用函数式组件开发的例子,加以说明。

使用 useState hook函数代替类函数中的state变量,简化了编程。

// src/component/ShortAnswerCard/ShortAnswerCard.jsx
function ShortAnswerCard(props) {
  const { classes, content, warning, callback } = props;
  const [input, setInput] = React.useState("");

  const handleChange = event => {
    setInput(event.target.value);
    callback(event.target.value);
  };

  return (
    <Card className={classes.card}>
      <CardContent>
        <Typography className={classes.title} variant="h5" component="h2">
          {content.title}
        </Typography>
        <TextField
          id="outlined-textarea"
          label="Answer"
          placeholder="Placeholder"
          multiline
          fullWidth
          className={classes.textField}
          margin="normal"
          variant="outlined"
          onChange={handleChange}
          error={error}
          helperText={warning && error ? "回答不可为空" : null}
        />
      </CardContent>
    </Card>
  );
}

使用 useEffect hook 函数进行模拟类组件的生命周期函数。

// src/view/TaskList/TaskList.jsx.... 
function TaskList(props) {

  //  模拟 componentDidMount
  React.useEffect(() => {
    const fetchData = async () => {
      const requestOptions = {
        method: "GET"
      };
      fetch(apiUrl + "/questionnaire/previews", requestOptions)
        .then(handleResponse)
        .then(response => {
          response = response.reverse();
          setTaskContent(response);
        });
    };
    fetchData(); // 请求数据
  }, []);

  return (
    <div>
      {taskContent.map(
        item =>
          item.inTrash !== 1 &&
          item.creator === localStorage.getItem("userID") && (
            <TaskContent
              taskName={item.taskName}
              taskID={item.taskID}
              taskType={item.taskType}
              money={item.money}
            />
          )
      )}
    </div>
  );
}

以上两种Hooks函数是在项目开发中常用的两个钩子,基于这两个接口函数,我们在大多数情况下可以编写像类组件一样的函数组件,简化组件的开发。

使用了函数式编程的组件还有以下列表,这里不做展开赘述。

src/view/TaskList/TaskList.jsx
src/view/TaskList/Tasksquare.jsx
src/view/TaskList/TaskBoard.jsx
src/view/TaskList/TaskArray.jsx
src/view/TaskList/QuestionPage.jsx
src/view/TaskList/Questionnaire.jsx
src/view/TaskList/Notification.jsx
src/view/TaskList/CreateTask.jsx
src/view/TaskList/Commission.jsx
....
src/component/*/*jsx

3. 容器组件和表现组件分离

React 组件通常包含杂合在一起的逻辑和表现。这里采用了React一种强大而简洁的模式,成为容器组件与表现组件,按照这种模式创建的组件,可以帮助我们分离逻辑和表现这两个关注点。

我们通常在容器组件中定义获取数据逻辑并存储,然后通过props传递数据给表现组件,表现组件接受数据进行渲染呈现UI。在这个模式中,每个组件都被拆分成若干个小组件,每一个组件都有各自清晰的职责。

这里以获取问卷页面为例。

/// src/views/QuestionPage
import SingleChoiceCard from "../../components/SingleChoiceCard/SingleChoiceCard";
import MultiChoiceCard from "../../components/MultiChoiceCard/MultiChoiceCard";
import ShortAnswerCard from "../../components/ShortAnswerCard/ShortAnswerCard";

// 容器组件 QuesitonPage
function QuestionPage(props) {
  // ...............
  const fetchQuestion = questionID => () => {
    const apiUrl = "https://littlefish33.cn:8080/questionnaire/select";
    const requestOption = {
      method: "POST",
      headers: {
        Accept: "application/json",
        "content-type": "application/x-www-form-urlencoded"
      },
      body: parseParams({ id: questionID })
    };

    fetch(apiUrl, requestOption)
      .then(handleResponse)
      .then(response => {
        setMoney(parseInt(response.money));
        setQuestionData(response.chooseData);
      });
  };

  React.useEffect(fetchQuestion(match.params.taskID), []);
    
  const createQuestionCard = (elem, index) => {
      // 渲染表现组件
      case 1:
        content = { ...content, ["title"]: elem.title };
        ret = (
          <ShortAnswerCard
            content={content}
            warning={warning && qdata[index].required}
            callback={setAns(index)}
          />
        );
        break;
	  // ......  其他表现组件
    }
    return ret;
  };
  return (
    <div>
      {qdata.map(createQuestionCard)}
    </div>
  );
}

容器组件和表现组件分离的设计方法,在以下文件同样应用到。

src/views/Quesionnaire/Quesionnaire.jsx
src/views/TaskList/TaskList.jsx
src/views/Quesionnaire/Quesionnaire.jsx
src/views/TaskSquare/TaskSquare.jsx
src/views/TaskArray/TaskArray.jsx
...

五、界面UI设计

UI原型设计工具:xiaopiu

UI设计展示链接:界面UI设计

六、 代码编程规范

JS规范

React/JSX 规范

基本

命名

扩展名: React 组件使用 .jsx 扩展名

代码缩进

括号

标签

函数/方法

生命周期顺序