【React】做一个百万答题小项目

  • 因为这个小项目是按照开源项目实现的,所以只了解React和Redux实现方式就好,相关的引入框架和后台数据库相关暂时先不解释。(主要是没时间了,肝deadline。。。)

1.安装框架和依赖

用的是蚂蚁ant框架

npm install antd-mobile --save

然后导入样式

import { Button } from 'antd-mobile';
import 'antd-mobile/dist/antd-mobile.css'; 

还要安装插件

npm install  babel-plugin-import --save

相关配置

npm run eject

package.json

"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      ["import", { "libraryName": "antd-mobile", "style": "css" }]
    ]
  }

项目数据

  1. 数据导入:
    Quizzes.json文件导入数据库。首先创建数据库,右键点击表,选择导入文件,文件导入json文件,后面一键跳过就行,最后一步点击完成。
  2. 创建服务器:
npm init 
cnpm install express --save
cnpm install mysql --save

创建index.js文件

  1. 前端依赖
    需要安装的内容:axios,react-router-dom,redux,react-redux,

相关目录树

在这里插入图片描述

2.数据库配置

  • sql.js
    设置连接,新建连接对象,let con = mysql.createConnection(connection);并定义promise对象查询的方法,返回为promise对象。
const mysql = require('mysql');

//配置连接
//这个自己设置哈
const connection = {
  host: 'localhost',
  post: '3306',
  user: 'root',
  password: 'root',
  database: 'timu'
};

//创建连接对象
//let con = mysql.createConnection(connection);

//连接
// con.connect(err => {
//   if (err) {
//     console.log('数据库连接失败');
//   } else {
//     console.log('数据库连接成功');
//   }
// });

//创建promise对象查询方法

function queryFn(sqlStr, arr) {
  //创建连接对象
  let con = mysql.createConnection(connection);
  return new Promise((resolve, reject) => {
    //找到了就返回并断开连接,没找到就拒绝连接
    con.query(sqlStr, arr, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
        con.end()
      }
    });
  });
}

module.exports = queryFn;

3.express实现数据获取

  • 通过express()前端页面的路由监听进行对应后端数据库端口的数据获取
  1. 创建express对象并绑定前端页面端口,为了传输数据
  2. 端口监听事件绑定过后就要进行页面URL的路由绑定,比如对这个路由'/api/rtimu/'进行数据的获取
  3. 这里通过异步请求进行获取,因为获取时是跨域的页面是“8080”端口但是服务器端是“3306”所以要进行跨域请求设置res.append("Access-Control-Allow-Origin","*") res.append("Access-Control-Allow-Content-Type","*")
  4. 通过sql语句进行数据库操作,这里sqlQuery的promise对象建立时就已经连接上数据库啦
  5. 然后数据转成json就可以给前端的store用啦

网页服务端的index.js

var express = require('express')
var app = express()
var sqlQuery = require('./sql')

app.get('/',(req,res)=>{
    res.send("这是答题API服务器")
})

//如果要获取第几页的数据 就 /:page
app.get('/api/rtimu/',async (req,res)=>{
    //随机获取10个题目;
    //console.log(req.query)
    //跨域请求
    res.append("Access-Control-Allow-Origin","*")
    res.append("Access-Control-Allow-Content-Type","*")
    //判断存不存在 存在就是page不存在就默认设为2
    let page = req.query.page?req.query.page:2;
    let strSql = `select * from quizzes limit ${page*10},10`;
    //这里要用到异步
    let result = await sqlQuery(strSql)
    //console.log(result)
	//Array.from(result)从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
    res.json(Array.from(result));


})

//后端监听事件
//有关app.listen的解释
/*
app.listen = function listen() {
    var server = http.createServer(this);
    return server.listen.apply(server, arguments);
};
//this就是下面
var app = function(req,res,next){}
*/
app.listen(8080,()=>{
    console.log(
        "server Start",
        "http://localhost:8080/"
    )
})

4.Store配置

ajax数据请求

  • store/asyncMethods.js
  • 通过随机生成page的值进行随机获取值
import axios from 'axios';
const host = 'http://localhost:8080' 
let fns = {
    async TmList(){
        let page = parseInt(Math.random()*1600);
        let httpUrl = `${host}/api/rtimu/?page=${page}`
        let res = await axios.get(httpUrl);
        return res.data
        //console.log(res.dat)
    }
}

export default fns;

Redux的state操作函数设置

  • method.js
  • 目的是为了在此对store中的state进行修改,也就是后面要调用到的dispatch
let methods = {
    add:function(state,action){
        state.num++
        return state
    },
    addNum:function(state,action){
        
        state.num = state.num + action.num;
        return state
    },
    setTimu:function(state,action){
        state.timuList = action.content;
        return state;
    }
}

export default methods

Redux的state设置

let state = {
    timuList:[],
}

export default state 

Redux的store相关操作(state初始化 reducer创建)

import {createStore} from 'redux';
import methods from './methods';
import state from './state';

let data = state;
//初始化state
//创建出reducer函数
let ActionFnObj=methods;
function reducer(state=data,action){
    if(action.type.indexOf('redux')===-1){
        state = ActionFnObj[action.type](state,action)
        return {...state}
    }else{
        return state;
    }
}

const store = createStore(reducer)

export default store

5.store(state和函数)到props的映射

  • 建立映射函数(store.state和store.(dispatch函数))
  • 然后通过connect方法进行组件类和store的绑定

我们通过这个思想来进行这个百万答题项目的页面渲染

  • 首先是页面导航页面:App.js
  • 通过映射获取state,但是本页面就直接跳转页面了,所以没有相关的store操作
import React from 'react';
import {connect} from 'react-redux'
import {Button} from 'antd-mobile'

//将state映射到props函数
function mapStateToProps(state){
    return {...state}
}


//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
    return {
        onAddClick:()=>{dispatch({type:'add'})},
        
            
    }
}

class Counter extends React.Component{
    
    render(){
        
        return (
            <div>
                <Button onClick={this.goDatiPage}>随机答题</Button>
                <Button onClick={this.props.onAddClick5}>闯关答题</Button>
                <Button onClick={this.props.onAddClick5}>抽奖答题</Button>
            </div>
        )


    }
    goDatiPage=()=>{
        //console.log(this.props)
        this.props.history.push("/dati")
    }
}

//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const App = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter)

export default App
  • 然后是答题页面Dati.js
  1. 首先是state和state操作函数的props映射,这里通过传入dispatch的值进行store中的方法匹配
  2. 然后进行react的题目DOM页面渲染,题目就直接从state获取就行了
  3. 然后题目的选项上面要绑定一个判断正确与错误的事件,也是通过对state里面的数据进行获取匹配实现的
  4. 最后答完题还要进行一个页面push约等于刷新的事件,进行下一题的操作,这里有个小技巧就是通过对state的currentnum进行修改然后就能够重新唤起渲染事件进行页面刷新,并且如果>10还能跳转result界面。
import React from 'react';
import {connect} from 'react-redux'
// import {Button} from 'antd-mobile'
import fns from '../store/asyncMethods'
import loadingImg from '../assets/img/loading.gif' 

//将state映射到props函数
function mapStateToProps(state){
    return {...state}
}


//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
    return {
        onAddClick:()=>{dispatch({type:'add'})},
        getTimu:async ()=>{
            let list = await fns.TmList()
            dispatch({
                type:"setTimu",
                content:list
            })
            console.log(list)
        }
    }
}

class DatiCom extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            currentTimu:0,
            optionsStyle:['optionItem','optionItem','optionItem','optionItem'],
            isChoose:false,
            score:0
        }
        
    }
    
    componentDidMount(){
        this.props.getTimu()
    }
    render(){
        console.log(this.props)
        console.log(this.state.currentTimu)
        let timuArr = this.props.timuList;
        let currentNum = this.state.currentTimu;
        let oStyle = this.state.optionsStyle;
        
        
        //如果数据没有加载进来,就设置为loading
        if(timuArr.length>0){
            let options = JSON.parse(timuArr[currentNum].options) 
            return (
                <div className="datiPage">
                    <h2>
                       {currentNum+1}-{timuArr[currentNum].quiz}
                    </h2>
                    <div className="options">
                        {
                            options.map((item,index)=>{
                                return (
                                    <div key={index} className={oStyle[index]} onClick={()=>this.answerEvent(index)}>
                                        {index+1}: {item}
                                    </div>
                                )
                            })
                        }
                    </div>
                </div>
            )
        }else{
            return (
                <div>
                    <img alt="img" src={loadingImg} />
                </div>
            )
            
        }
        


    }
    goDatiPage=()=>{
        //console.log(this.props)
        this.props.history.push("/dati")
    }
    answerEvent=(index)=>{
        if(this.state.isChoose){
            return true;
        }

        console.log(index)
        let currentAnswer = this.props.timuList[this.state.currentTimu].answer;
        console.log(currentAnswer)
        let score = this.state.score;
        if((index+1)===Number(currentAnswer)){
            let optionsStyle = this.state.optionsStyle;
            optionsStyle[index] = "optionItem correct";
            this.setState({
                optionsStyle:optionsStyle,
                isChoose:true,
                score:score+10
            })
        }else{
            let optionsStyle = this.state.optionsStyle;
            optionsStyle[index] = "optionItem error";
            optionsStyle[(Number(currentAnswer)-1)] = "optionItem correct"
            this.setState({
                optionsStyle:optionsStyle,
                isChoose:true
            })
        }
        //2秒跳转至下一题
        setTimeout(() => {
            let currentNum = this.state.currentTimu
            currentNum++
            if(currentNum===10){
                this.props.history.push('/result',{score:this.state.score})
            }else{
                this.setState({
                    currentTimu:currentNum,
                    optionsStyle:['optionItem','optionItem','optionItem','optionItem'],
                    isChoose:false
                })

            }
            
        }, 2000);
        
    }
}

//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const Dati = connect(
    mapStateToProps,
    mapDispatchToProps
)(DatiCom)

export default Dati
  • result.js
  • 实现获取props的state数据进行分数统计
import React from 'react';
import {connect} from 'react-redux'
import {Button} from 'antd-mobile'

//将state映射到props函数
function mapStateToProps(state){
    return {...state}
}


//将修改state数据的方法,映射到props,默认会传入store里的dispach方法
function mapDispatchToProps(dispatch){
    return {
        onAddClick:()=>{dispatch({type:'add'})},
        
            
    }
}

class Counter extends React.Component{
    
    render(){
        console.log(this.props)
        return (
            <div>
                <h1>恭喜您获得{this.props.location.state.score}</h1>
                <Button onClick={this.goDatiPage}>回到首页</Button>
                
            </div>
        )


    }
    goDatiPage=()=>{
        //console.log(this.props)
        this.props.history.push("/")
    }
}

//将上面的这2个方法,将数据仓库的state和修改state的方法映射到组件上,形成新的组件。
const App = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter)

export default App
  • index.js 主渲染文件
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux'
import {BrowserRouter as Router,Route} from 'react-router-dom';
import store from './store/data'
import App from './view/App'

import './assets/css/style.css'

import Dati from './view/Dati'
import Result from './view/Result'
ReactDOM.render(
    <Provider store={store}>
        <Router>
            <Route path="/" exact component={App}></Route>
            <Route path="/dati" component={Dati}></Route>
            <Route path="/result" component={Result}></Route>
        </Router>
    </Provider>,
    document.querySelector("#root")
)


最后再来点css特效

/* .datiPage{

} */
.datiPage h2{
    width: 90%;
    margin: 10px auto;
}
.options{
    width: 90%;
    display: flex;
    flex-direction: column;
    height: 400px;
    margin: 20px auto;
    border-radius: 10px;
    background-color: #efefef;
    justify-content: space-around;
    align-items: center;

}

.optionItem{
    width: 90%;
    height: 60px;
    line-height: 60px;
    padding: 0 10px;
    background-color: lightblue;
    border-radius: 10px;
}

.optionItem.correct{
    background-color: lightgreen;
    color: #fff;
}

.optionItem.error{
    background-color: orangered;
    color: #fff;
}


谢谢阅读,over!

posted @ 2020-10-18 21:56  嗨Sirius  阅读(113)  评论(0编辑  收藏  举报