[筆記] React 隨手記 (React 應用篇hooks、setInterval、.map、取陣列或物件的方法)


Posted by stella572322 on 2021-05-17

圖片說明:
public:專門放圖片、照片
src底下放
component管理元件
config管理設定
hooks管理邏輯

//在 component 底下的 carousel.js 檔案(定位為專門處理標籤區)
import positionArray from '../../config/positionArray';// positionArray 檔案定位等同於旗杆位置(資料設計成陣列再包陣列,下圖一)
import prizeArray from '../../config/prizeArray';//prizeArray 檔案定位等同於標籤資料(資料設計成陣列再包物件,下圖二)
import useCarousel from '../../hooks/useCarousel.js';
//把 hooks 資料夾下的 useCarousel 引入,在下面取得 prize的資料
import {
  ItemContainer,
  FirstMedal,
  Item,
  Percentage,
  Percent,
  CarouselContainer,
  CarouselItems,
} from './style';//引入 css 

export function CarouselItem({
//把會大量重複的 component 獨立出來
//這裡其實取到得值是從 prizeArray (圖二)設計好的資料,
把 key 傳入,傳輸順序是 prizeArray =>  Carousel => CarouselItem
  percent,
  picture,
  medal,
  top,
  left,
  width,
  height,
  opacity,
}) {
  return (
   //因為 這裡的參數 要傳給 css 那邊利用 props 接
    <ItemContainer $top={top} $left={left}>
      <FirstMedal $medal={medal}></FirstMedal>
      <Item
      //因為 這裡的參數 要傳給 css 那邊利用 props 接
        $picture={picture}
        $width={width}
        $height={height}
        $opacity={opacity}
      >
        <Percentage>
         //因為 這裡的參數 只要顯示出來 所以直接接在標籤
          <Percent>{percent}%</Percent>
        </Percentage>
      </Item>
    </ItemContainer>
  );
}
export default function Carousel() {
  const { prizes, startMove } = useCarousel();
//從 hook (useCarousel)拿 prizes 的資料
//資料來源 prizeArray =>  prizes => useCarousel
  useEffect(() => {
    startMove(0, {
      left: positionArray[1][0],
      //key是left陣列第一個的第0個值 => 276
    });
  }, []);

  useEffect(() => {
    console.log(prizes);
  }, []);
  return (
    <>
      <CarouselContainer>
        <CarouselItems>
        //利用.map 條列是渲染到畫面上
          {prizes.map((prize) => {
            return (
              <CarouselItem
                key={prize.id}//記得要給 key,通常都是設id為key
                picture={prize.picture}//利用.物件取值
                left={prize.left}
                top={prize.top}
                zindex={prize.zindex}
                width={prize.width}
                height={prize.height}
                opacity={prize.opacity}
                medal={prize.medal}
                percent={prize.percent}
              />
            );
          })}
        </CarouselItems>
      </CarouselContainer>
    </>
  );
}
  • positionArray 檔案定位等同於旗杆位置(資料設計成陣列再包陣列,下圖一)
const positionArray = [
  [133, -177, '0', 129, 123, 0.4],
  [276, -72, '2', 138, 124, 0.6],
];
export default positionArray;
  • prizeArray 檔案定位等同於標籤資料(資料設計成陣列再包物件,下圖二)
const prizeArray = [
  {
    id: 1,
    picture: 1,
    left: 133,
    top: -177,
    zindex: '0',
    width: 129,
    height: 123,
    opacity: 0.4,
    medal: null,
    percent: 21,
  },
  {
    id: 2,
    picture: 2,
    left: 276,
    top: -72,
    zindex: '2',
    width: 138,
    height: 124,
    opacity: 0.6,
    medal: null,
    percent: 21,
  },
];
export default prizeArray;
import { useState, useEffect } from 'react';
import positionArray from '../config/positionArray';
import prizeArray from '../config/prizeArray';

export default function useCarousel() {
const [prizes, setPrizes] = useState(prizeArray);
//把 prizeArray 的資料傳進來給 prizes 用
const startMove = (target, json) => {
//寫 startMove function 
//target 代表獎品標籤,在陣列第幾個,以數字表示
//json 代表獎品 position
clearInterval(prizes[target].timer);
//.timer類似計數器的概念 是跟 setInterval 配合著使用
//為了防止 setInterval 重新開始跑的時候,
但前面的還沒跑完又開始會壞掉,才設定先停止,讓後面開始時,不會壞掉
prizes[target].timer = setInterval(function () {
  var intervalContinue = false;//預設標籤還沒到指定位子
  for (var attr in json) {
  //var attr in json ( json 物件裡面的 key 值依序賦值到 attr)
  //attr 表示 prizeArray => 陣列底下 => 物件底下 => key的值 
  //ex: prizeArray[1]['left']  => left 值等於 276

  setPrizes((prizes) => {

 //寫一個 setPrizes 的非同步 function 裡面包各種 if else 條件所回傳的情況,
 //裡面接 prizes 參數,因為上面的回圈跑得很快,
 //下面又是非同步的 function,
 //為了讓每次回圈跑完 prizes 的資料,進到非同步裡是正確的,
 //避免忽略前一圈 setPrize 的改變,且每次 cur 都要用到新的值
 //所以這裡才要先接 prizes 確保接到前一圈改動完的資料,
 //例如:上面的回圈是跑 left ,非同步的 function也是接到 改完的 left 的值

  let cur;
    /* 抓取調整前的數值 cur */
    if (attr === 'opacity') {
      cur = Math.round(parseFloat(prizes[target] [`${attr}`]) * 100); 
      } else {
      cur = prizes[target][`${attr}`];
    }
    console.log(`調整前的 ${attr} :`, cur);

    /* 計算調整的幅度 speed */
      let speed =
      attr === 'opacity'
      ? (json[attr] * 100 - cur) / 6
      : (json[attr] - cur) / 6;
      speed = speed > 0 
      ? Math.ceil(speed) : Math.floor(speed);
      console.log('調整幅度 speed:', speed);

      /* 判斷是否已經調整到目標位置 */
      if (attr === 'opacity' && cur / 100 !== json[attr]) intervalContinue = true;
      //標籤還沒到指定位子就繼續
      if (attr !== 'opacity' && cur !== json[attr]) intervalContinue = true;
      //標籤還沒到指定位子就繼續

      /* 開始進行調整 */
      console.log(`調整後的 ${attr} :`,
        attr === 'opacity' 
        ? (cur + speed) / 100 
        : cur + speed
      );

    return prizes.map((item, prizeIndex) => {
    //利用 .map 遍歷 prizes下面的物件,接收每一圈 item, prizeIndex 的值    
      if (prizeIndex !== index) return item;
    //判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果不是就回傳原本的樣子
        return {
        ...item,//展開
        [attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,//三元運算值判斷 ture 或 false
        };
    });
    if (continue) {
      clearInterval(prizes[target].timer);
      }
    }, 0);
  };
  return {
    prizes,
    startMove,
  };
}
  • 移動修正:多個動畫移動殘障問題

  • 原本:
    原本把遍歷 position 的回圈寫在 setPrize 外,每一次的回圈跑完都會進行一次 setPrize 調整好一張圖 key 的值
    慢慢移動到他指定的位子
    由於每次setPrize都會等待上一圈改動
    每一圈都要等前一圖調好,下一個圖調整,造成動畫移動 lag 的問題,

  • 解決方法:降低 setPrize 執行的次數,從每次一個一個 key 慢慢改,變成五個 key 全部調完再一次 setPrize

  • 改動:
    寫一個 tempObject 放每一圈回圈跑完的 key 值
    把 for 回圈改在 setPrizes 裡面
    讓每一圈所有圖片的 key 值 都一起改動
    最後再把每一圈改變每個圖的 key 值資料
    全部 return 出去給 setPrizes

import { useState, useEffect } from 'react';
import positionArray from '../config/positionArray';
import prizeArray from '../config/prizeArray';

export default function useCarousel() {
const [prizes, setPrizes] = useState(prizeArray);

const startMove = (index, json) => {
clearInterval(prizes[index].timer);
prizes[index].timer = setInterval(function () {
  console.log('===============分隔線=================');
  var intervalContinue = false;
  setPrizes((prizes) => {
    let tempObject = prizes[index];
    //寫一個 tempObject 放每一圈回圈跑完的 key 值
    //把 for 回圈改在 setPrizes 裡面
    console.log(tempObject);

    for (let attr in json) {
      let cur;
      /* 抓取調整前的數值 cur */
      if (attr === 'opacity') {
        cur = Math.round(parseFloat(tempObject[`${attr}`]) * 100);
      } else {
        cur = tempObject[`${attr}`];
      }
      console.log(`調整前的 ${attr} :`, cur);

      /* 計算調整的幅度 speed */
      let speed =
        attr === 'opacity'
          ? (json[attr] * 100 - cur) / 1.5
          : (json[attr] - cur) / 1.5;
      speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
      console.log('調整幅度 speed:', speed);

      /* 判斷是否已經調整到目標位置 */
      if (attr === 'opacity' && cur / 100 !== json[attr])
        intervalContinue = true;
      if (attr !== 'opacity' && cur !== json[attr])
        intervalContinue = true;

      /* 開始進行調整 */
      console.log(
        `調整後的 ${attr} :`,
        attr === 'opacity' ? (cur + speed) / 100 : cur + speed
      );
      tempObject = {
      //把每個回圈展開更新的資料賦值到 tempObject
        ...tempObject,
        [attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,
      };
    }
    return prizes.map((item, prizeIndex) => {
        //回圈結束後,用 .map 遍歷 prizes下面的物件,接收每一圈 item, prizeIndex 的值  
      if (prizeIndex !== index) return item;
          //判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果不是就回傳原本的樣子
      return tempObject;
          //判斷 prizeIndex 的圖,是否不等於,這一圈 startMove 對應要改變的對象,如果是就回傳 tempObject 每一圈跑完的資料
    });
  });
  console.log('intervalContinue: ', intervalContinue);
      if (!intervalContinue) {
        clearInterval(prizes[index].timer);
      }
    }, 0);
  };
  return {
    prizes,
    startMove,
  };
}
  • 增加功能:
    • 游標 hover 到圖片,輪播圖停止轉動
    • 往前 往後 按鈕
    • 自動輪播功能
export default function useCarousel() {
  const [prizes, setPrizes] = useState(prizeArray);
  const [movePosition, setMovePosition] = useState(positionArray);
  //為了 button 寫一個資料會動的 Position 資料 => positionArray
  //因為 react 寫在 config 的資料,基本上是希望資料是不會變更的
  //所以另外複製一份 positionArry 資料,讓 movePosition 使用

  const [isHover, setIsHover] = useState(false);
//製作功能:游標 hover 到圖片,輪播圖停止
//預設 isHover 為 false

  const handleClickPreButton = () => {
  //事件監聽
    setMovePosition((movePosition) =>
      [movePosition[6], ...movePosition].filter((_, index) => index !== 7)
      //先把最後一個放到第一個
      //接著按順序排列
      //利用.filter篩選出不是第七個的都留起來
    );
  };

  const handleClickNextButton = () => {
  //事件監聽
    setMovePosition((movePosition) =>
      [...movePosition, movePosition[0]].filter((_, index) => index !== 0)
      //按順序排列
      //先把第一個放到最後一個
      //利用.filter篩選出不是第0個的都留起來
    );
  };

const startMove = useCallback((index, json) => {
clearInterval(prizes[index].timer);
prizes[index].timer = setInterval(function () {
  var intervalContinue = false;
  setPrizes((prizes) => {
    let tempObject = prizes[index];
    for (let attr in json) {
      let cur;
      /* 抓取調整前的數值 cur */
      if (attr === 'opacity') {
        cur = Math.round(parseFloat(tempObject[`${attr}`]) * 100);
      } else {
        cur = tempObject[`${attr}`];
      }

      /* 計算調整的幅度 speed */
      let speed =
        attr === 'opacity'
          ? (json[attr] * 100 - cur) / 1.5
          : (json[attr] - cur) / 1.5;
      speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);

      /* 判斷是否已經調整到目標位置 */
      if (attr === 'opacity' && cur / 100 !== json[attr])
        intervalContinue = true;
      if (attr !== 'opacity' && cur !== json[attr]) intervalContinue = true;
      tempObject = {
        ...tempObject,
        [attr]: attr === 'opacity' ? (cur + speed) / 100 : cur + speed,
      };
    }
    return prizes.map((item, prizeIndex) => {
      if (prizeIndex !== index) return item;
      return tempObject;
    });
  });
      if (!intervalContinue) {
        clearInterval(prizes[index].timer);
      }
    }, 0);
}, []);

  useEffect(() => {
    for (let i = 0; i < 7; i++) {
    //因為共6張圖,故寫一個回圈跑7次
      let j = i === 6 ? 0 : i + 1;
      //當 i === 6 就讓 i = 0
      //當 i !== 6 就讓 i = i+1
      //把結果賦值給 j
      startMove(i, {
        left: movePosition[j][0],
        top: movePosition[j][1],
        width: movePosition[j][2],
        height: movePosition[j][3],
        opacity: movePosition[j][4],
      });
    }
  }, [movePosition, startMove]);

  return {
    prizes,
    movePosition,
    isHover,
    setIsHover,
    setMovePosition,
    startMove,
    handleClickNextButton,
    handleClickPreButton,
  };
}
export default function Carousel() {
  let moveTimer = useRef();
  //把 useRef() 賦值到變數 moveTimer 上
  //useRef() 建立出來的變數在被改變時並不會觸發 re-render,
  //也就表示當數值變化後並無法即時呈現在畫面中
  //useRef 只會回傳一個值,這個值是一個有 current屬性 的 Object 。

  const {
    prizes,
    isHover,
    setIsHover,
    handleClickPreButton,
    handleClickNextButton,
  } = useCarousel();
  //把 hook 資料引進來的方式

  useEffect(() => {
  //偵測到 isHover 資料改變時,
    if (isHover) clearInterval(moveTimer.current);
    //判斷 isHover === true,使用 瀏覽器提供的 clearInterval,
    //用 .currn 拿到物件裡的資料
    if (!isHover) moveTimer.current = setInterval(handleClickNextButton, 2000);
    //判斷 isHover === false,
    //使用 瀏覽器提供的 setInterval,
    //讓 handleClickNextButton 每隔兩秒觸發一次(形成圖片自動輪播狀態)
  }, [isHover]);

  return (
    <>
      <CarouselContainer>
        <CarouselItems
          onMouseOver={() => {
          //react 提供的事件監聽指令
          //監聽滑鼠有沒有 hover 在元件的上方
            setIsHover(true);
            //如果滑鼠 hover 在元件的上方 setIsHover(true) 改成 true
          }}
          onMouseLeave={() => {
          //react 提供的事件監聽指令
          //監聽滑鼠有沒有離開 hover 元件
            setIsHover(false);
            //如果滑鼠 hover 在元件的上方 setIsHover(false) 改成 false
          }}
        >
          {prizes.map((prize) => {
            return (
              <CarouselItem
                key={prize.id}
                picture={prize.picture}
                left={prize.left}
                top={prize.top}
                width={prize.width}
                height={prize.height}
                opacity={prize.opacity}
                medal={prize.medal}
                percent={prize.percent}
              />
            );
          })}
        </CarouselItems>
        <CarouselButton>
        //在標籤上加上聽器
          <CarouselPrevButton onClick={handleClickPreButton} />
          //在標籤上加上聽器
          <CarouselNextButton onClick={handleClickNextButton} />
        </CarouselButton>
      </CarouselContainer>
    </>
  );
}









Related Posts

[Day01] immutable

[Day01] immutable

Day 46 - create Spotify playlist

Day 46 - create Spotify playlist

Mysql 狀況紀錄 8/11

Mysql 狀況紀錄 8/11


Comments