基于React的狀態(tài)管理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的顏色轉(zhuǎn)換器
初探 React 鉤子
React 訪問 DOM 節(jié)點(diǎn)
在 React 中,要想直接訪問 DOM 節(jié)點(diǎn)需要使用 React 的一項(xiàng)特性—— ref。
ref:是一個(gè)對(duì)象,存儲(chǔ)一個(gè)組件整個(gè)生命周期內(nèi)的值。
在 React 中,React 為我們提供了 useRef 鉤子 來創(chuàng)建 ref。
import { useRef } from "react"; export default function ChangeColorForm() { const hexColor = useRef(); const rgbColor = useRef(); const getFullHexColorStr = str => { if (isNaN(parseInt(str, 16))) { alert("請(qǐng)輸入正確的十六進(jìn)制顏色值"); return ''; } if (str.length === 3) { return str.split('').map(item => item + item).join(''); } else { const lastChar = str.charAt(str.length - 1); const strArr = [...Array(5)]; return str + strArr.map(s => lastChar).join(""); } } const toggleColor = e => { e.preventDefault(); const hexColorStr = hexColor.current.value; const rgbColorStr = rgbColor.current.value; if (hexColorStr) { const hexColor = getFullHexColorStr(hexColorStr).toUpperCase(); if (!hexColor) { return; } const strArr = [...Array(6)]; const rgbColorArr = strArr.reduce((pre, cur, index) => { if (index % 2 === 1) { pre = [...pre, parseInt(hexColor.substring(index - 1, index + 1), 16)]; } return pre; },[]); rgbColor.current.value = rgbColorArr.join(','); } else { if (rgbColorStr) { const rgbColorArr = rgbColorStr.split(','); const isRightLength = rgbColorArr.length === 3; const isRightRGB = rgbColorArr.every(colorNum => !isNaN(Number(colorNum) && Number(colorNum) >= 0 && Number(colorNum) <= 255)) if (isRightLength && isRightRGB) { const hexColorArr = rgbColorArr.map(colorNum => Number(colorNum).toString(16)); hexColor.current.value = hexColorArr.join('').toUpperCase(); } else { alert("請(qǐng)輸入正確的rgb格式顏色值"); } } } }; return( <form onSubmit={toggleColor} style={{padding: '20px'}}> <label htmlFor="hexColor">十六進(jìn)制格式顏色:</label> <input name="hexColor" ref={hexColor} type="text" placeholder="請(qǐng)輸入十六進(jìn)制格式顏色"></input> <br/> <br/> <label htmlFor="rgbColor">rgb格式顏色:</label> <input name="rgbColor" ref={rgbColor} type="text" placeholder="請(qǐng)輸入rgb格式顏色"></input> <button>轉(zhuǎn)換</button> </form> ) }
這里我們手動(dòng)寫了一個(gè)關(guān)于十六進(jìn)制顏色值與RGB顏色值互轉(zhuǎn)的簡(jiǎn)易工具,代碼里邊我們來看一看 useRef 鉤子的用法,我們用 useRef 鉤子創(chuàng)建了兩個(gè) ref,分別在 JSX 中 input 標(biāo)簽中添加 ref 屬性,那么我們就不再需要通過選擇器來獲取 DOM 元素了,直接在 ref 對(duì)象中去取 DOM 元素就可以了,從代碼中我們可以看到,這個(gè) DOM 元素存儲(chǔ)在 ref 對(duì)象中的 current 屬性中。
受控組件
在 React 中,受控組件就是以組件內(nèi)部狀態(tài)來管理組件內(nèi)部值得變化的組件。而上一節(jié)我們提到了組件的狀態(tài)有 React 提供的 useState 鉤子來進(jìn)行控制,我們可以理解成受控組件就是由狀態(tài)來控制屬性值變化得組件,由父組件屬性傳遞和事件來驅(qū)動(dòng)狀態(tài)的變化,使數(shù)據(jù)產(chǎn)生流動(dòng)的效果。當(dāng)然,數(shù)據(jù)流動(dòng)的效果也注定使組件不斷的重新渲染,這一點(diǎn),我們也必須要了解。
既然這樣,我們來讓這個(gè)顏色碼值轉(zhuǎn)換表單組件改成一個(gè)受控組件:
import { useState } from "react"; export default function ChangeColorForm() { const [hexColorStr, setHexColorStr] = useState('FFFFFF'); const [rgbColorStr, setRgbColorStr] = useState(''); const getFullHexColorStr = str => { if (isNaN(parseInt(str, 16))) { alert("請(qǐng)輸入正確的十六進(jìn)制顏色值"); return ''; } if (str.length === 3) { return str.split('').map(item => item + item).join(''); } else { const lastChar = str.charAt(str.length - 1); const strArr = [...Array(5)]; return str + strArr.map(() => lastChar).join(""); } } const toggleColor = e => { e.preventDefault(); if (hexColorStr) { const hexColor = getFullHexColorStr(hexColorStr).toUpperCase(); if (!hexColor) { return; } const strArr = [...Array(6)]; const rgbColorArr = strArr.reduce((pre, cur, index) => { if (index % 2 === 1) { pre = [...pre, parseInt(hexColor.substring(index - 1, index + 1), 16)]; } return pre; },[]); setRgbColorStr(rgbColorArr.join(',')); } else { if (rgbColorStr) { const rgbColorArr = rgbColorStr.split(','); const isRightLength = rgbColorArr.length === 3; const isRightRGB = rgbColorArr.every(colorNum => !isNaN(Number(colorNum) && Number(colorNum) >= 0 && Number(colorNum) <= 255)) if (isRightLength && isRightRGB) { const hexColorArr = rgbColorArr.map(colorNum => Number(colorNum).toString(16)); setHexColorStr(hexColorArr.join('').toUpperCase()); } else { alert("請(qǐng)輸入正確的rgb格式顏色值"); } } } }; return( <form onSubmit={toggleColor} style={{padding: '20px'}}> <label htmlFor="hexColor">十六進(jìn)制格式顏色:</label> <input name="hexColor" type="text" value={hexColorStr} placeholder="請(qǐng)輸入十六進(jìn)制格式顏色" onChange={e => {setHexColorStr(e.target.value)}}></input> <br/> <br/> <label htmlFor="rgbColor">rgb格式顏色:</label> <input name="rgbColor" type="text" value={rgbColorStr} placeholder="請(qǐng)輸入rgb格式顏色" onChange={e => {setRgbColorStr(e.target.value)}}></input> <button>轉(zhuǎn)換</button> </form> ) }
在組件中,通過 React 的狀態(tài)來保存兩個(gè) input 元素的值,只要觸發(fā)了 onChange 事件,通過參數(shù) e(event).target 來獲取 DOM,獲取元素值再通過狀態(tài)設(shè)置函數(shù)來將狀態(tài)值更新,使得 DOM 重新渲染,將值又賦值進(jìn)輸入框,形成一個(gè)閉合回路。
如果感覺看代碼太麻煩,難以理解,我們來看看示意圖:
我們把代碼拿出來理一理就能得到一個(gè)很簡(jiǎn)單的邏輯圖,是不是一目了然。
上面我們利用狀態(tài)創(chuàng)建的受控組件中,input 元素只有兩個(gè),但是在實(shí)際的開發(fā)中,對(duì)應(yīng)的 input 元素可能有十多二十個(gè)甚至更多,按照這樣的方法,那是不是得這樣重復(fù)操作很多次,之前我們提到過,遇到重復(fù)的,結(jié)構(gòu)相似的代碼,考慮一下是否可以抽象出來封裝一下呢?下面我們來介紹一下自定義的鉤子。
自定義鉤子
從上面的代碼我們可以看到,定義狀態(tài),input 元素中賦值 value,onChange 方法,他們的結(jié)構(gòu)是不是基本都是一樣的呢?我們先來看看定義狀態(tài)是不是可以抽象為:
// initValue 初始值參數(shù) const [value, setValue] = useState(initValue);
對(duì)于 input 元素中的屬性賦值,我們還能想起屬性較多的時(shí)候,整體屬性的傳遞方法嗎?估計(jì)能想起來{...props}
;那我們能否把 value,跟 onChange 封裝在一個(gè)對(duì)象里邊呢?然后用一個(gè)函數(shù)返回回來。我們新建一個(gè) hooks.js文件,來看看怎么封裝。
// hooks.js import { useState } from "react"; export const useInput = initValue => { const [value, setValue] = useState(initValue); return [ {value, onChange: (e) => setValue(e.target.value)}, (custVal = initValue) => setValue(custVal) ] }
一眼看完,可能還沒明白為啥要這樣封裝,先暫時(shí)不說,我們還是再來把顏色轉(zhuǎn)換器繼續(xù)優(yōu)化一下,再來看看為啥這么封裝?
import { useInput } from "./hooks"; export default function ChangeColorForm() { const [hexColorProps, setHexColorStr] = useInput('FFFFFF'); const [rgbColorProps, setRgbColorStr] = useInput(''); const getFullHexColorStr = str => { if (isNaN(parseInt(str, 16))) { alert("請(qǐng)輸入正確的十六進(jìn)制顏色值"); return ''; } if (str.length === 3) { return str.split('').map(item => item + item).join(''); } else { const lastChar = str.charAt(str.length - 1); const strArr = [...Array(5)]; return str + strArr.map(() => lastChar).join(""); } } const toggleColor = e => { e.preventDefault(); if (hexColorProps.value) { const hexColor = getFullHexColorStr(hexColorProps.value).toUpperCase(); if (!hexColor) { return; } const strArr = [...Array(6)]; const rgbColorArr = strArr.reduce((pre, cur, index) => { if (index % 2 === 1) { pre = [...pre, parseInt(hexColor.substring(index - 1, index + 1), 16)]; } return pre; },[]); setRgbColorStr(rgbColorArr.join(',')); } else { if (rgbColorProps.value) { const rgbColorArr = rgbColorProps.value.split(','); const isRightLength = rgbColorArr.length === 3; const isRightRGB = rgbColorArr.every(colorNum => !isNaN(Number(colorNum) && Number(colorNum) >= 0 && Number(colorNum) <= 255)) if (isRightLength && isRightRGB) { const hexColorArr = rgbColorArr.map(colorNum => Number(colorNum).toString(16)); setHexColorStr(hexColorArr.join('').toUpperCase()); } else { alert("請(qǐng)輸入正確的rgb格式顏色值"); } } } }; const resetClick = e => { e.preventDefault(); setHexColorStr(); } return( <form onSubmit={toggleColor} style={{padding: '20px'}}> <label htmlFor="hexColor">十六進(jìn)制格式顏色:</label> <input {...hexColorProps} name="hexColor" type="text" placeholder="請(qǐng)輸入十六進(jìn)制格式顏色"></input> <button onClick={resetClick}>重置</button> <br/> <br/> <label htmlFor="rgbColor">rgb格式顏色:</label> <input {...rgbColorProps} name="rgbColor" type="text" placeholder="請(qǐng)輸入rgb格式顏色"></input> <button>轉(zhuǎn)換</button> </form> ) }
我們先來看看這兩行代碼的對(duì)比:
// useState React 鉤子 const [hexColorStr, setHexColorStr] = useState('FFFFFF'); // useInput 自定義鉤子 const [hexColorProps, setHexColorStr] = useInput('FFFFFF');
useState 鉤子我們已經(jīng)用得比較熟悉,熟悉了它的用法與結(jié)構(gòu),當(dāng)我們需要封裝自定義鉤子的時(shí)候,當(dāng)函數(shù)能返回跟原始鉤子保持一致的結(jié)構(gòu)時(shí),當(dāng)在使用的時(shí)候,是不是就會(huì)更加的輕松呢?值得注意的是,這里的 hexColorProps 是包含了 value, onChange 的多屬性的屬性值,在使用的時(shí)候我們是需要注意跟 useState 定義的屬性值得區(qū)別。
在這次的代碼中,我故意添加了一個(gè)重置按鈕,估計(jì)用意你們已經(jīng)猜到,很直白,就是為了講解一下 hooks.js 中的這個(gè)方法:
(custVal = initValue) => setValue(custVal)
隨著 ESNext 的不斷更新,前端的代碼也是越來越簡(jiǎn)單,但是只要我們慢慢分析,還是很好理解的,我們來看一下對(duì)應(yīng) ES5 的代碼:
function (custVal) { custVal = custVal ? custVal : initValue; return setValue(custVal) }
在實(shí)際的開發(fā)中,會(huì)遇到比較多的表單的聯(lián)動(dòng)與重置,不難看出這個(gè)方法,就是為給 input 元素提供外部事件(非input 元素的 onChange 事件)來更改或重置 input元素的 value 屬性值。
總結(jié)
ref:是一個(gè)對(duì)象,存儲(chǔ)一個(gè)組件整個(gè)生命周期內(nèi)的值,DOM 元素存儲(chǔ)在 ref 對(duì)象中的 current 屬性中;
受控組件:是由狀態(tài)來控制屬性值變化得組件,由父組件屬性傳遞和事件來驅(qū)動(dòng)狀態(tài)的變化,使數(shù)據(jù)產(chǎn)生流動(dòng)的效果;
自定義鉤子:根據(jù)已知組件,方法的封裝,在封裝時(shí)盡可能保持與原組件,方法的參數(shù),返回值結(jié)構(gòu)保持一致。
以上就是基于React的狀態(tài)管理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的顏色轉(zhuǎn)換器的詳細(xì)內(nèi)容,更多關(guān)于React實(shí)現(xiàn)顏色轉(zhuǎn)換器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React中常見的動(dòng)畫實(shí)現(xiàn)的幾種方式
本篇文章主要介紹了React中常見的動(dòng)畫實(shí)現(xiàn)的幾種方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01React+TypeScript項(xiàng)目中使用CodeMirror的步驟
CodeMirror被廣泛應(yīng)用于許多Web應(yīng)用程序和開發(fā)工具,之前做需求用到過codeMirror這個(gè)工具,覺得還不錯(cuò),功能很強(qiáng)大,所以記錄一下改工具的基礎(chǔ)用法,對(duì)React+TypeScript項(xiàng)目中使用CodeMirror的步驟感興趣的朋友跟隨小編一起看看吧2023-07-07簡(jiǎn)單的React SSR服務(wù)器渲染實(shí)現(xiàn)
這篇文章主要介紹了簡(jiǎn)單的React SSR服務(wù)器渲染實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12hooks寫React組件的5個(gè)注意細(xì)節(jié)詳解
這篇文章主要為大家介紹了hooks寫React組件的5個(gè)需要注意的細(xì)節(jié)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03使用react在修改state中的數(shù)組和對(duì)象數(shù)據(jù)的時(shí)候(setState)
這篇文章主要介紹了使用react在修改state中的數(shù)組和對(duì)象數(shù)據(jù)的時(shí)候(setState),具有很好的參考價(jià)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09解決react中l(wèi)abel標(biāo)簽for報(bào)錯(cuò)問題
這篇文章主要介紹了react中l(wèi)abel標(biāo)簽for報(bào)錯(cuò)問題,解決辦法就是react中l(wèi)abel標(biāo)簽沒有for屬性,用htmlFor代替for屬性,感興趣的朋友跟隨小編一起看看吧2022-02-02react-router-dom6(對(duì)比?router5)快速入門指南
這篇文章主要介紹了快速上手react-router-dom6(對(duì)比?router5),通過本文學(xué)習(xí)最新的react-router-dom?v6版本的路由知識(shí),并且會(huì)與v5老版本進(jìn)行一些對(duì)比,需要的朋友可以參考下2022-08-08