zoukankan      html  css  js  c++  java
  • 【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!

  • 相关阅读:
    【leetcode】1215.Stepping Numbers
    【leetcode】1214.Two Sum BSTs
    【leetcode】1213.Intersection of Three Sorted Arrays
    【leetcode】1210. Minimum Moves to Reach Target with Rotations
    【leetcode】1209. Remove All Adjacent Duplicates in String II
    【leetcode】1208. Get Equal Substrings Within Budget
    【leetcode】1207. Unique Number of Occurrences
    【leetcode】689. Maximum Sum of 3 Non-Overlapping Subarrays
    【leetcode】LCP 3. Programmable Robot
    【leetcode】LCP 1. Guess Numbers
  • 原文地址:https://www.cnblogs.com/SiriusZHT/p/14310770.html
Copyright © 2011-2022 走看看