React实现的穿梭框

1,022 阅读4分钟

React的第一次小demo

之前一直是在做VUE的,几年前学习的React也都随时间忘记了,最近收到了一家面试题,要求用react去实现一个穿梭框功能,再拾起多年不用的react+几个hooks钩子函数将这个demo完成;这个demo过程中使用到了最基本的react函数组件渲染,父子组件传出惨,定义状态等,踩了许多坑,对这次的代码做一个记录。

要求效果如下:

image.png

image.png

首先需求进行分析,这个简单的穿梭狂拥有4个功能,分别是:1.向右,向左移动全部的数据 2.向右,向左移动被选中的数据。那就要拥有两个组件,一个组件控制左边的数据,一个组件控制右边数据框的数据了。

这是APP组件得代码

import UseRightData from './rightItem';
import React, { useState,useRef } from "react";
import './App.css';

function App() {
  const leftData = useState([])
  const rightData = useState([])
  let rightInfo = useRef(null);
  let leftInfo = useRef(null);
// 全部向右移动
  function moveToRight() {
    //调用右边组件方法,将左边数组的内容当成参数转移传递过去
    rightInfo.current.changRight([...rightInfo.current.rightArr,...leftInfo.current.leftArr]);
    // 再将左边的数组清空
    leftInfo.current.changLeft([])
  }
  //向右移动选中的
  function moveSlectToRight() {
    // 下标如果不对,或者没获取到就不做操作
    if (leftInfo.current.leftArrIndex === -1 || leftInfo.current.leftArrIndex === undefined) {
      return
    }
    // 如果没获取到元素也不做操作
    if (!leftInfo.current.leftArr[leftInfo.current.leftArrIndex]) {
      return
    }
    // 右边数组
    let newRightArr = rightInfo.current.rightArr
    let newLeftArr = leftInfo.current.leftArr
    newRightArr.push(leftInfo.current.leftArr[leftInfo.current.leftArrIndex])
    // 先从左边数据数组中找到对应的元素进行添加到右边数组去
    rightInfo.current.changRight([...newRightArr])
    // 再把原来数组数据删除
    newLeftArr.splice(leftInfo.current.leftArrIndex, 1)
    leftInfo.current.changLeft([...newLeftArr])
    // 再将下标初始化,否则能选中后面的
    leftInfo.current.restIndex()
  }

  // 全部向左移动
  function moveToLeft() {
    //调用左边组件方法,将右边数组的内容当成参数转移传递过去
    leftInfo.current.changLeft([...rightInfo.current.rightArr,...leftInfo.current.leftArr]);
    // 再将左边的数组清空
    rightInfo.current.changRight([])
  }
  //向左移选中的
  function moveSlectToLeft() {
    if (rightInfo.current.rightArrIndex === -1 || rightInfo.current.rightArrIndex === undefined) {
        return
    }
    let newLeftArr = leftInfo.current.leftArr
    let newRightArr = rightInfo.current.rightArr
    newLeftArr.push(rightInfo.current.rightArr[rightInfo.current.rightArrIndex])
    // 先从右边数据数组中找到对应的元素进行添加到左边数组去
    leftInfo.current.changLeft([...newLeftArr])
    // 再把原来数组数据删除
    newRightArr.splice(rightInfo.current.rightArrIndex, 1)
    rightInfo.current.changRight([...newRightArr])
    // 再将下标初始化,否则能选中后面的
    rightInfo.current.restIndex()
  }

  return (
    <div className="App">
    <div className="container">
        <div className="left">
            <h3>xxx的现任女友</h3>
            <select id="sel1" multiple>
            <UseLeftData leftdata={leftData} leftInfo={ leftInfo }></UseLeftData>
            </select>
        </div>
        <div className="left mid">
            <p>
                <button id="btnToRight" title="右移动选中的" onClick={ moveSlectToRight }>&gt;&gt;</button>
            </p>
            <p>
            <button id="btnToRightAll" title="右移动全部" onClick={ moveToRight }>&gt;&gt;|</button>
            </p>
            <p>
                <button id="btnToLeft" title="左移动选中的" onClick={ moveSlectToLeft }>&lt;&lt;</button>
            </p>
            <p>
                <button id="btnToLeftAll" title="左移动全部" onClick={ moveToLeft }>|&lt;&lt;</button>
            </p>
        </div>
        <div className="left">
            <h3>xxx的前女友</h3>
            <select id="sel2" multiple>
                <UseRightData rightData = { rightData } rightInfo={ rightInfo } ></UseRightData>
            </select>
        </div>
      </div>
      </div>
  );
}

export default App;

这里是对应得左边数据框leftItem.js得代码


const useLeftData = (props) => {
    const [leftArr, setLeftArr] = useState(['幂幂', '花花', '春春', '盈盈'])
    // 声明选中的选项下标
    const [arrIndex,setIndex] = useState(-1)
    const { leftInfo } = props
    // 挂载到父组件的方法和数据
    // leftArr就是暴露给父组件的数据
    useImperativeHandle(leftInfo, () => ({
        // changLeft 就是暴露给父组件的方法
        changLeft: newVal => {
            setLeftArr(newVal)
        },
        restIndex: newVal => {
            setIndex(-1)
        },
        leftArr,
        leftArrIndex:arrIndex
    }));
    // 将选中的选项下标传赋值
    function selectData(index) {
        setIndex(index)
    }
    return (leftArr.map((item, index) => {
        return <option value={index} key={item} onClick={ selectData.bind(this,index) }> {item} </option>
        })
    )
}
export default useLeftData

这里是右边数据框rightItem.js的代码


const useRightData = (props) => {
    //右边数组数据
    const [rightArr, setRightArr] = useState(['坤坤'])
    //单选下标
    const [arrIndex,setIndex] = useState(-1)
    const { rightInfo } = props
  // 挂载到父组件的方法和数据
    useImperativeHandle(rightInfo, () => ({
        // changRight 就是暴露给父组件的方法
        // rightArr就是暴露给父组件的数据
        changRight: newVal => {
            console.log(newVal);
            setRightArr(newVal)
        },
        restIndex: newVal => {
            setIndex(-1)
        },
        rightArr,
        rightArrIndex:arrIndex
    }));
    // 因为useState是异步的,因此要用useEffect去监听数据的变化
    useEffect(() => {
        
    },[arrIndex])

    // 选中选项改变下标赋值
    function selectData(index) {
        if (index === -1) {
            return
        } else {
            setIndex(index)
        }
    }
    return (rightArr.map((item, index) => {
        return <option value={index} key={item} onClick={ selectData.bind(this,index) }> {item} </option>
        })
    )
}
export default useRightData

最后得app.css代码

  overflow: hidden;
  width: 550px;
  margin: 0 auto;
}

.container .left {
  float: left;
  margin: 0 30px;
}

.container select {
  width: 100%;
  height: 200px;
}

.container .mid {
  padding-top: 70px;
}

代码结束,如果有需要得同学可以用脚手架创建一个项目后,把app.js、app.css、leftItem.js、rightItem.js放入src文件夹下即可

踩到的坑:

  1. 在使用useState时,只知道可以提供一个状态出来以及改变状态的方法,但是不知道useState改变状态的方法是一个异步方法,然后在负组件中改变值后,打印的值依旧是上一次状态的值,改了许久才发现的问题,如果需要解决这个办法需要使用useEffect对某一个状态进行一个监听,这样监听状态发生改变后再做某些操作,但很巧的是,这个需求后来分析发现不需要用useEffect,当然可能用这个会更简单,但因为本身自己的不熟,所以没有用到。
  2. 这次demo使用的组件传参是父穿子用props,子传父使用到了useRef以及useImperativeHandle两个钩子函数。
  • 使用useRef创建一个变量状态,然后状态绑定给子组件
  • 然后子组件中使用useuseImperativeHandle拿到对应的组件ref实例,然后把子组件中的状态和方法挂在到这个ref实例中
  • 在父组件中使用实例如rightInfo.current就能获取到被useImperativeHandle挂载到的实例数据