React的第一次小demo
之前一直是在做VUE的,几年前学习的React也都随时间忘记了,最近收到了一家面试题,要求用react去实现一个穿梭框功能,再拾起多年不用的react+几个hooks钩子函数将这个demo完成;这个demo过程中使用到了最基本的react函数组件渲染,父子组件传出惨,定义状态等,踩了许多坑,对这次的代码做一个记录。
要求效果如下:
首先需求进行分析,这个简单的穿梭狂拥有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 }>>></button>
</p>
<p>
<button id="btnToRightAll" title="右移动全部" onClick={ moveToRight }>>>|</button>
</p>
<p>
<button id="btnToLeft" title="左移动选中的" onClick={ moveSlectToLeft }><<</button>
</p>
<p>
<button id="btnToLeftAll" title="左移动全部" onClick={ moveToLeft }>|<<</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文件夹下即可
踩到的坑:
- 在使用useState时,只知道可以提供一个状态出来以及改变状态的方法,但是不知道useState改变状态的方法是一个异步方法,然后在负组件中改变值后,打印的值依旧是上一次状态的值,改了许久才发现的问题,如果需要解决这个办法需要使用useEffect对某一个状态进行一个监听,这样监听状态发生改变后再做某些操作,但很巧的是,这个需求后来分析发现不需要用useEffect,当然可能用这个会更简单,但因为本身自己的不熟,所以没有用到。
- 这次demo使用的组件传参是父穿子用props,子传父使用到了useRef以及useImperativeHandle两个钩子函数。
- 使用useRef创建一个变量状态,然后状态绑定给子组件
- 然后子组件中使用useuseImperativeHandle拿到对应的组件ref实例,然后把子组件中的状态和方法挂在到这个ref实例中
- 在父组件中使用实例如rightInfo.current就能获取到被useImperativeHandle挂载到的实例数据