• 【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!

  • 相关阅读:
    转:sql语句中GROUP BY 和 HAVING和使用 count()
    shell中的大括号和小括号
    转:关于rename命令ubuntu下的用法
    Linux批量重命名
    STL 源代码剖析 算法 stl_algo.h -- partition
    HDU 5091 线段树扫描线
    IBM 中国研究院面试经历
    当人手一部智能手机时 庞大的数据中心们已死
    Treap的读书笔记2
    【JUnit4.10源码分析】5 Statement
  • 原文地址:https://www.cnblogs.com/SiriusZHT/p/14310770.html
Copyright © 2020-2023  润新知