In this post I will try to compare the two frameworks by developing in each of them the minesweeper game.
Here is the code for the minesweeper game with React:
import { useEffect, useRef, useState } from 'react'
import Minesweeper from './Minesweeper'
const App = () => {
const initialDim = 2
const [dim, setDim] = useState(initialDim)
const [Dim, SetDim] = useState(initialDim)
const [foo, setFoo] = useState(0)
const [minesweeper, setMinesweeper] = useState(
<Minesweeper key={Math.random()} dim={Dim} />
)
const reset = () => {
setMinesweeper(<Minesweeper key={Math.random()} dim={Dim} />)
}
const inputRef = useRef(null)
const setDimension = () => {
SetDim(inputRef.current.value)
setFoo((foo) => foo + 1)
}
useEffect(() => {
reset()
}, [foo])
return (
<div>
{minesweeper}
<button onClick={reset}>reset</button>
<input
type="text"
value={dim}
ref={inputRef}
onChange={(event) => {
setDim(event.target.value)
}}
/>
<button onClick={setDimension}>set dim</button>
</div>
)
}
export default App
Previous was code for App.js. We have also Minesweeper.js and Cell.js (also for Vue 3).
import styled from 'styled-components'
import Cell from './Cell'
import { useEffect, useRef, useState } from 'react'
const Minesweeper = ({ dim }) => {
const arrayRefs = new Array(dim)
for (let i = 0; i < dim; i++) {
arrayRefs[i] = new Array(dim)
}
const _minesAround = new Array(dim)
for (let i = 0; i < dim; i++) {
_minesAround[i] = new Array(dim)
}
const [minesAround, setMinesAround] = useState(_minesAround)
const computeMinesAround = () => {
for (let i = 0; i < dim; i++) {
for (let j = 0; j < dim; j++) {
let numOfMines = 0
infoRef.current[i - 1] &&
infoRef.current[i - 1][j - 1] &&
infoRef.current[i - 1][j - 1].isMined &&
numOfMines++
infoRef.current[i - 1] &&
infoRef.current[i - 1][j] &&
infoRef.current[i - 1][j].isMined &&
numOfMines++
infoRef.current[i - 1] &&
infoRef.current[i - 1][j + 1] &&
infoRef.current[i - 1][j + 1].isMined &&
numOfMines++
infoRef.current[i] &&
infoRef.current[i][j - 1] &&
infoRef.current[i][j - 1].isMined &&
numOfMines++
infoRef.current[i] &&
infoRef.current[i][j + 1] &&
infoRef.current[i][j + 1].isMined &&
numOfMines++
infoRef.current[i + 1] &&
infoRef.current[i + 1][j - 1] &&
infoRef.current[i + 1][j - 1].isMined &&
numOfMines++
infoRef.current[i + 1] &&
infoRef.current[i + 1][j] &&
infoRef.current[i + 1][j].isMined &&
numOfMines++
infoRef.current[i + 1] &&
infoRef.current[i + 1][j + 1] &&
infoRef.current[i + 1][j + 1].isMined &&
numOfMines++
const newMinesAround = [...minesAround]
newMinesAround[i][j] = numOfMines
setMinesAround(newMinesAround)
}
}
}
useEffect(() => {
computeMinesAround()
}, [])
const infoRef = useRef(arrayRefs)
const _cellRefs = new Array(dim)
for (let i = 0; i < dim; i++) {
_cellRefs[i] = new Array(dim)
}
const cellRefs = useRef(_cellRefs)
// we prepare the board
const board = new Array(dim)
for (let i = 0; i < dim; i++) {
board[i] = new Array(dim)
}
for (let i = 0; i < dim; i++) {
for (let j = 0; j < dim; j++) {
board[i][j] = (
<Cell
key={`${i}_${j}`}
numOfMines={minesAround[i][j]}
ref={(item) => (cellRefs.current[i][j] = item)}
cellRefs={cellRefs}
i={i}
j={j}
infoRef={infoRef}
/>
)
}
}
return (
<Div>
{board.map((row, i) => (
<Row key={i}>{row}</Row>
))}
</Div>
)
}
export default Minesweeper
const Div = styled.div`
font-family: sans-serif;
`
const Row = styled.div`
display: flex;
`
Previous was Minesweeper.js. Finally, for React, we have Cell.js:
import styled from 'styled-components'
import { useEffect, useState, forwardRef } from 'react'
const Cell = forwardRef(
({ numOfMines, cellRefs, i, j, infoRef }, ref) => {
const [isCovered, setIsCovered] = useState(true)
const [isMined,] = useState(Math.random() > 0.8)
useEffect(() => {
infoRef.current[i][j] = {isMined,isCovered}
}, [isCovered,isMined])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i - 1] &&
infoRef.current[i - 1][j - 1] &&
infoRef.current[i - 1][j - 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i - 1][j - 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i - 1] &&
infoRef.current[i - 1][j - 1] &&
infoRef.current[i - 1][j - 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i - 1] &&
infoRef.current[i - 1][j] &&
infoRef.current[i - 1][j].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i - 1][j].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i - 1] &&
infoRef.current[i - 1][j] &&
infoRef.current[i - 1][j].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i - 1] &&
infoRef.current[i - 1][j + 1] &&
infoRef.current[i - 1][j + 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i - 1][j + 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i - 1] &&
infoRef.current[i - 1][j + 1] &&
infoRef.current[i - 1][j + 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i] &&
infoRef.current[i][j - 1] &&
infoRef.current[i][j - 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i][j - 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i] &&
infoRef.current[i][j - 1] &&
infoRef.current[i][j - 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i] &&
infoRef.current[i][j + 1] &&
infoRef.current[i][j + 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i][j + 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i] &&
infoRef.current[i][j + 1] &&
infoRef.current[i][j + 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i + 1] &&
infoRef.current[i + 1][j - 1] &&
infoRef.current[i + 1][j - 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i + 1][j - 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i + 1] &&
infoRef.current[i + 1][j - 1] &&
infoRef.current[i + 1][j - 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i + 1] &&
infoRef.current[i + 1][j] &&
infoRef.current[i + 1][j].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i + 1][j].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i + 1] &&
infoRef.current[i + 1][j] &&
infoRef.current[i + 1][j].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
useEffect(() => {
if (
numOfMines === 0 &&
isMined === false &&
infoRef.current[i + 1] &&
infoRef.current[i + 1][j + 1] &&
infoRef.current[i + 1][j + 1].isCovered &&
infoRef.current[i][j].isCovered === false
) {
cellRefs.current[i + 1][j + 1].click()
}
}, [
isCovered,
numOfMines,
isMined,
infoRef.current[i + 1] &&
infoRef.current[i + 1][j + 1] &&
infoRef.current[i + 1][j + 1].isCovered,
infoRef.current[i][j] && infoRef.current[i][j].isCovered,
])
const uncover = () => {
if (isCovered) {
setIsCovered(false)
}
}
return (
<Div isCovered={isCovered} onClick={uncover} ref={ref}>
{isCovered ? '' : isMined ? '😄' : numOfMines === 0 ? '' : numOfMines}
</Div>
)
}
)
export default Cell
const Div = styled.div`
border-radius: 5px;
width: 18px;
height: 18px;
margin: 2px;
cursor: pointer;
${({ isCovered }) => `
${isCovered ? 'background-color:grey;' : ''}
`}
`
So that's it for React. Next we are going to see the same files for Vue 3:
import {defineComponent,ref} from 'vue'
import {Minesweeper} from './Minesweeper'
export const App=defineComponent(()=>{
const inputRef=ref()
const dim=ref()
const setDimension=()=>{
dim.value=inputRef.value.value
}
return ()=>{
return <>
<input ref={inputRef} /><button onClick={setDimension}>set Dimension</button>
<Minesweeper dim={dim}/>
</>
}
})
Previous was App.tsx. Next we are going to see Minesweeper.tsx for Vue 3:
import { defineComponent,ref,onMounted,onUpdated } from 'vue'
import {Cell} from './Cell'
import styled from 'vue3-styled-components'
interface IProps{
dim:any;
}
export const Minesweeper= defineComponent((props:IProps)=>{
const infoRefs=ref<any[]>(new Array(props.dim.value))
onUpdated(()=>{
for(let i=0;i<props.dim.value;i++){
for(let j=0;j<props.dim.value;j++){
let minesAround=0
if(infoRefs.value[i-1]&&infoRefs.value[i-1][j-1])
infoRefs.value[i-1][j-1].isMined&&minesAround++
if(infoRefs.value[i-1]&&infoRefs.value[i-1][j])
infoRefs.value[i-1][j].isMined&&minesAround++
if(infoRefs.value[i-1]&&infoRefs.value[i-1][j+1])
infoRefs.value[i-1][j+1].isMined&&minesAround++
if(infoRefs.value[i]&&infoRefs.value[i][j-1])
infoRefs.value[i][j-1].isMined&&minesAround++
if(infoRefs.value[i]&&infoRefs.value[i][j+1])
infoRefs.value[i][j+1].isMined&&minesAround++
if(infoRefs.value[i+1]&&infoRefs.value[i+1][j-1])
infoRefs.value[i+1][j-1].isMined&&minesAround++
if(infoRefs.value[i+1]&&infoRefs.value[i+1][j])
infoRefs.value[i+1][j].isMined&&minesAround++
if(infoRefs.value[i+1]&&infoRefs.value[i+1][j+1])
infoRefs.value[i+1][j+1].isMined&&minesAround++
infoRefs.value[i][j]={...infoRefs.value[i][j],minesAround}
}
}
})
return ()=>{
const board=new Array(props.dim.value)
for(let i=0;i<props.dim.value;i++){
infoRefs.value[i]=new Array(props.dim.value)
board[i]=new Array(props.dim.value)
for(let j=0;j<props.dim.value;j++){
infoRefs.value[i][j]={}
board[i][j]=<Cell key={`${props.dim.value}_${i}_${j}`} infoRefs={infoRefs} i={i} j={j} />
}
}
return <>
{board.map(row=><Row>{row}</Row>)}
</>
}
})
Minesweeper.props={
dim:{
type:Object
}
}
const Row=styled.div`
display:flex;
`
Finally, the last file for Vue 3 would be Cell.tsx:
import { defineComponent,onMounted,onUpdated,ref } from 'vue'
import styled from 'vue3-styled-components'
interface IProps{
infoRefs:any;
i:number;
j:number;
}
export const Cell= defineComponent((props:IProps)=>{
const isMined=ref(Math.random()>0.9)
const cellRef=ref()
onMounted(()=>{
props.infoRefs.value[props.i][props.j]={...props.infoRefs.value[props.i][props.j],isMined,cellRef,isCovered}
})
onUpdated(()=>{
if(!isCovered.value&&!isMined.value&&props.infoRefs.value[props.i][props.j].minesAround===0){
props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j-1]&&props.infoRefs.value[props.i-1][props.j-1].isCovered&&props.infoRefs.value[props.i-1][props.j-1].cellRef.$el.click()
props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j]&&props.infoRefs.value[props.i-1][props.j].isCovered&&props.infoRefs.value[props.i-1][props.j].cellRef.$el.click()
props.infoRefs.value[props.i-1]&&props.infoRefs.value[props.i-1][props.j+1]&&props.infoRefs.value[props.i-1][props.j+1].isCovered&&props.infoRefs.value[props.i-1][props.j+1].cellRef.$el.click()
props.infoRefs.value[props.i]&&props.infoRefs.value[props.i][props.j-1]&&props.infoRefs.value[props.i][props.j-1].isCovered&&props.infoRefs.value[props.i][props.j-1].cellRef.$el.click()
props.infoRefs.value[props.i]&&props.infoRefs.value[props.i][props.j+1]&&props.infoRefs.value[props.i][props.j+1].isCovered&&props.infoRefs.value[props.i][props.j+1].cellRef.$el.click()
props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j-1]&&props.infoRefs.value[props.i+1][props.j-1].isCovered&&props.infoRefs.value[props.i+1][props.j-1].cellRef.$el.click()
props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j]&&props.infoRefs.value[props.i+1][props.j].isCovered&&props.infoRefs.value[props.i+1][props.j].cellRef.$el.click()
props.infoRefs.value[props.i+1]&&props.infoRefs.value[props.i+1][props.j+1]&&props.infoRefs.value[props.i+1][props.j+1].isCovered&&props.infoRefs.value[props.i+1][props.j+1].cellRef.$el.click()
}
})
const isCovered=ref(true)
const unCover=()=>{
isCovered.value=false
props.infoRefs.value[props.i][props.j]={...props.infoRefs.value[props.i][props.j],isCovered}
console.log(`clicked ${props.i}_${props.j}`)
}
return ()=>{
return <>
<_Cell isCovered={isCovered.value} onClick={unCover} ref={cellRef}>{isCovered.value?'':isMined.value?'😁':props.infoRefs.value[props.i][props.j].minesAround===0?'':props.infoRefs.value[props.i][props.j].minesAround}</_Cell>
</>
}
})
Cell.props={
infoRefs:{
type:Object
},
i:{
type:Number
},
j:{
type:Number
}
}
const _CellProps={
isCovered:Boolean
}
const _Cell=styled('div',_CellProps)`
${({isCovered}:any)=>`
${isCovered?`
background-color:grey;
`:''}
`}
width:18px;
height:18px;
border-radius:5px;
margin:5px;
cursor:pointer;
`
So that was all. Thanks for watching.
ps: for those who want to check which one is more performant, copy the code, implement it in a project, run it, put a dimension of one hundred, and see what happens 😉 (you might get surprised)
ps2: keywords: vue 3, react, jsx, benchmark, minesweeper, performance
Top comments (0)