การสร้างไลบรารีการตรวจสอบ React ของคุณเอง: คุณลักษณะ (ตอนที่ 2)

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างรวดเร็ว ↬ ในบทความก่อนหน้าของ Kristofer เขาอธิบายว่าส่วนพื้นฐานของไลบรารีการตรวจสอบความถูกต้องสามารถนำไปใช้ได้อย่างไร ในขณะที่ส่วนต่อไปจะเน้นที่การปรับปรุงประสบการณ์ของนักพัฒนา บทความของวันนี้จะเน้นที่การเพิ่มคุณสมบัติเพิ่มเติมให้กับสิ่งที่สร้างขึ้นในส่วนที่ 1

การใช้ไลบรารีตรวจสอบความถูกต้องนั้นไม่ได้ยากขนาดนั้น ไม่ได้เพิ่มคุณสมบัติพิเศษทั้งหมดที่ทำให้ห้องสมุดตรวจสอบ ของคุณ ดีกว่าที่เหลือ

บทความนี้จะใช้ไลบรารีการตรวจสอบความถูกต้องต่อไปที่เราเริ่มนำไปใช้ในส่วนก่อนหน้าของชุดบทความนี้ นี่คือคุณสมบัติที่จะนำเราจากการพิสูจน์แนวคิดง่ายๆ ไปสู่ห้องสมุดที่ใช้งานได้จริง!

  • ตอนที่ 1: พื้นฐาน
  • ส่วนที่ 2: คุณสมบัติ
  • ตอนที่ 3: ประสบการณ์

แสดงเฉพาะการตรวจสอบเมื่อส่ง

เนื่องจากเรากำลังตรวจสอบเหตุการณ์การเปลี่ยนแปลงทั้งหมด เราจึงแสดงข้อความแสดงข้อผิดพลาดของผู้ใช้เร็วเกินไปสำหรับประสบการณ์ที่ดีของผู้ใช้ มีสองสามวิธีที่เราสามารถบรรเทาปัญหานี้ได้

วิธีแก้ปัญหาแรกคือการจัดเตรียมแฟล็กที่ submitted เป็นคุณสมบัติที่ส่งคืนของ useValidation hook ด้วยวิธีนี้ เราสามารถตรวจสอบได้ว่าแบบฟอร์มถูกส่งหรือไม่ก่อนที่จะแสดงข้อความแสดงข้อผิดพลาด ข้อเสียคือ "แสดงรหัสข้อผิดพลาด" ของเรายาวขึ้นเล็กน้อย:

 <label> Username <br /> <input {...getFieldProps('username')} /> {submitted && errors.username && ( <div className="error">{errors.username}</div> )} </label>

อีกวิธีหนึ่งคือการจัดเตรียมข้อผิดพลาดชุดที่สอง (เรียกมันว่า submittedErrors ) ซึ่งเป็นอ็อบเจกต์ว่างหาก submitted เป็นเท็จ และอ็อบเจ็กต์ errors หากเป็นจริง เราสามารถนำไปปฏิบัติได้ดังนี้

 const useValidation = config => { // as before return { errors: state.errors, submittedErrors: state.submitted ? state.errors : {}, }; }

ด้วยวิธีนี้ เราสามารถทำลายประเภทของข้อผิดพลาดที่เราต้องการแสดงได้ง่ายๆ แน่นอน เราทำสิ่งนี้ได้ที่ไซต์การโทรเช่นกัน — แต่ด้วยการให้ข้อมูลที่นี่ เรากำลังดำเนินการเพียงครั้งเดียว แทนที่จะใช้ภายในผู้บริโภคทั้งหมด

  • ดูการสาธิต CodeSandbox ที่แสดงวิธีการใช้ข้อผิดพลาดที่ submittedErrors
เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

แสดงข้อความแสดงข้อผิดพลาด On-Blur

หลายคนต้องการให้แสดงข้อผิดพลาดเมื่อออกจากฟิลด์ใดฟิลด์หนึ่ง เราสามารถเพิ่มการสนับสนุนสำหรับสิ่งนี้ โดยการติดตามว่าฟิลด์ใดที่ "เบลอ" (นำทางออกจาก) และส่งคืนอ็อบเจกต์ blurredErrors คล้ายกับข้อผิดพลาดที่ submittedErrors ด้านบน

การใช้งานต้องการให้เราจัดการกับการกระทำประเภทใหม่ - blur ซึ่งจะอัปเดตวัตถุสถานะใหม่ที่เรียกว่า blurred :

 const initialState = { values: {}, errors: {}, blurred: {}, submitted: false, }; function validationReducer(state, action) { switch (action.type) { // as before case 'blur': const blurred = { ...state.blurred, [action.payload]: true }; return { ...state, blurred }; default: throw new Error('Unknown action type'); } }

เมื่อเราส่งการดำเนินการ blur เราจะสร้างคุณสมบัติใหม่ในออบเจ็กต์สถานะ blurred โดยมีชื่อฟิลด์เป็นคีย์ ซึ่งบ่งชี้ ว่า ฟิลด์นั้นถูกเบลอ

ขั้นตอนต่อไปคือการเพิ่ม onBlur prop ให้กับฟังก์ชัน getFieldProps ซึ่งจะส่งการกระทำนี้เมื่อทำได้:

 getFieldProps: fieldName => ({ // as before onBlur: () => { dispatch({ type: 'blur', payload: fieldName }); }, }),

สุดท้าย เราจำเป็นต้องจัดเตรียม blurredErrors จากเบ็ด useValidation เพื่อให้เราสามารถแสดงข้อผิดพลาดได้เมื่อจำเป็นเท่านั้น

 const blurredErrors = useMemo(() => { const returnValue = {}; for (let fieldName in state.errors) { returnValue[fieldName] = state.blurred[fieldName] ? state.errors[fieldName] : null; } return returnValue; }, [state.errors, state.blurred]); return { // as before blurredErrors, };

ที่นี่ เราสร้างฟังก์ชันบันทึกที่คำนวณข้อผิดพลาดที่จะแสดงโดยพิจารณาว่าฟิลด์นั้นเบลอหรือไม่ เราคำนวณข้อผิดพลาดชุดนี้ใหม่ทุกครั้งที่ข้อผิดพลาดหรือวัตถุเบลอเปลี่ยนไป คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ useMemo hook ได้ในเอกสารประกอบ

  • ดูการสาธิต CodeSandbox

เวลาสำหรับ Refactor เล็กๆ

คอมโพเนนต์ useValidation ของเรากำลังส่งคืนข้อผิดพลาดสามชุด ซึ่งส่วนใหญ่จะดูเหมือนเดิมในบางช่วงเวลา แทนที่จะไปตามเส้นทางนี้ เราจะให้ผู้ใช้ระบุในการกำหนดค่าเมื่อพวกเขาต้องการให้ข้อผิดพลาดในแบบฟอร์มปรากฏขึ้น

ตัวเลือกใหม่ของเรา — showErrors — จะยอมรับ “ส่ง” (ค่าเริ่มต้น), “เสมอ” หรือ “เบลอ” เราสามารถเพิ่มตัวเลือกเพิ่มเติมได้ในภายหลัง ถ้าจำเป็น

 function getErrors(state, config) { if (config.showErrors === 'always') { return state.errors; } if (config.showErrors === 'blur') { return Object.entries(state.blurred) .filter(([, blurred]) => blurred) .reduce((acc, [name]) => ({ ...acc, [name]: state.errors[name] }), {}); } return state.submitted ? state.errors : {}; } const useValidation = config => { // as before const errors = useMemo( () => getErrors(state, config), [state, config] ); return { errors, // as before }; };

เนื่องจากโค้ดจัดการข้อผิดพลาดเริ่มกินพื้นที่ส่วนใหญ่ของเรา เรากำลังจัดโครงสร้างใหม่ให้เป็นฟังก์ชันของตัวเอง ถ้าคุณไม่ปฏิบัติตาม Object.entries และ .reduce ไม่เป็นไร — เป็นการเขียนโค้ด for...in ใหม่ในส่วนสุดท้าย

หากเราต้องการ onBlur หรือการตรวจสอบทันที เราสามารถระบุตัวแสดงข้อผิด useValidation showError เราได้

 const config = { // as before showErrors: 'blur', }; const { getFormProps, getFieldProps, errors } = useValidation(config); // errors would now only include the ones that have been blurred
  • ดูการสาธิต CodeSandbox

หมายเหตุเกี่ยวกับสมมติฐาน

“โปรดทราบว่าตอนนี้ฉันกำลังสมมติว่าแต่ละแบบฟอร์มต้องการแสดงข้อผิดพลาดในลักษณะเดียวกัน (เมื่อส่งเสมอ เบลอเสมอ ฯลฯ) นั่นอาจเป็นจริงสำหรับแอปพลิเคชันส่วนใหญ่ แต่อาจไม่ใช่สำหรับทุกคน การตระหนักถึงสมมติฐานของคุณเป็นส่วน สำคัญ ในการสร้าง API ของคุณ”

อนุญาตสำหรับการตรวจสอบข้าม

คุณลักษณะที่มีประสิทธิภาพจริงๆ ของไลบรารีการตรวจสอบความถูกต้องคือการอนุญาตให้มีการตรวจสอบข้าม นั่นคือ เพื่อกำหนดการตรวจสอบความถูกต้องของฟิลด์หนึ่งบนค่าของฟิลด์อื่น

ในการอนุญาตนี้ เราจำเป็นต้องทำให้ hook แบบกำหนดเองของเรายอมรับฟังก์ชันแทนที่จะเป็นอ็อบเจกต์ ฟังก์ชันนี้จะถูกเรียกใช้ด้วยค่าฟิลด์ปัจจุบัน การใช้งานจริงเป็นเพียงโค้ดสามบรรทัดเท่านั้น!

 function useValidation(config) { const [state, dispatch] = useReducer(...); if (typeof config === 'function') { config = config(state.values); } }

ในการใช้คุณลักษณะนี้ เราสามารถส่งฟังก์ชันที่ส่งคืนอ็อบเจ็กต์การกำหนดค่าไปยัง useValidation :

 const { getFieldProps } = useValidation(fields => ({ password: { isRequired: { message: 'Please fill out the password' }, }, repeatPassword: { isRequired: { message: 'Please fill out the password one more time' }, isEqual: { value: fields.password, message: 'Your passwords don\'t match' } } }));

ที่นี่ เราใช้ค่า fields.password เพื่อให้แน่ใจว่าสองฟิลด์รหัสผ่านมีอินพุตเดียวกัน (ซึ่งเป็นประสบการณ์การใช้งานที่แย่มาก แต่สำหรับโพสต์บล็อกอื่น)

  • ดูการสาธิต CodeSandbox ที่ไม่ยอมให้ชื่อผู้ใช้และรหัสผ่านเป็นค่าเดียวกัน

เพิ่มการช่วยสำหรับการเข้าถึงบางรายการ

สิ่งที่ควรทำเมื่อคุณรับผิดชอบอุปกรณ์ประกอบฉากของสนามคือการเพิ่มแท็กเพลงที่ถูกต้องตามค่าเริ่มต้น ซึ่งจะช่วยให้โปรแกรมอ่านหน้าจอสามารถอธิบายแบบฟอร์มของคุณได้

การปรับปรุงที่ง่ายมากคือการเพิ่ม aria-invalid="true" หากฟิลด์มีข้อผิดพลาด มาปฏิบัติกัน:

 const useValidation = config => { // as before return { // as before getFieldProps: fieldName => ({ // as before 'aria-invalid': String(!!errors[fieldName]), }), } };

นั่นเป็นโค้ดที่เพิ่มเข้ามา หนึ่ง บรรทัด และประสบการณ์ผู้ใช้ที่ดีขึ้น มาก สำหรับผู้ใช้โปรแกรมอ่านหน้าจอ

คุณอาจสงสัยว่าทำไมเราถึงเขียน String(!!state.errors[fieldName]) ? state.errors[fieldName] เป็นสตริง และตัวดำเนินการปฏิเสธสองครั้งจะให้บูลีนแก่เรา (และไม่ใช่แค่ค่าความจริงหรือเท็จ) อย่างไรก็ตาม คุณสมบัติ aria-invalid ควรเป็นสตริง (นอกจากนี้ยังสามารถอ่าน "ไวยากรณ์" หรือ "การสะกดคำ" นอกเหนือจาก "จริง" หรือ "เท็จ") ดังนั้นเราจึงจำเป็นต้องบังคับบูลีนนั้นให้เทียบเท่ากับสตริง

ยังมีการปรับแต่งอีกสองสามอย่างที่เราสามารถทำได้เพื่อปรับปรุงการช่วยสำหรับการเข้าถึง แต่ดูเหมือนว่าจะเป็นการเริ่มต้นที่ยุติธรรม

ไวยากรณ์ข้อความตรวจสอบชวเลข

เครื่องมือตรวจสอบส่วนใหญ่ใน calidators ตัววัด (และตัวตรวจสอบอื่น ๆ ส่วนใหญ่ฉันคิดว่า) ต้องการเพียงข้อความแสดงข้อผิดพลาด คงจะดีไม่น้อยถ้าเราสามารถส่งผ่านสตริงนั้นแทนอ็อบเจ็กต์ที่มีคุณสมบัติ message ที่มีสตริงนั้นได้

มาปรับใช้ในฟังก์ชัน validateField ของเรา:

 function validateField(fieldValue = '', fieldConfig, allFieldValues) { for (let validatorName in fieldConfig) { let validatorConfig = fieldConfig[validatorName]; if (typeof validatorConfig === 'string') { validatorConfig = { message: validatorConfig }; } const configuredValidator = validators[validatorName](validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }

ด้วยวิธีนี้ เราสามารถเขียนการกำหนดค่าการตรวจสอบของเราใหม่ได้ดังนี้:

 const config = { username: { isRequired: 'The username is required', isEmail: 'The username should be a valid email address', }, };

สะอาดขึ้นมาก!

ค่าฟิลด์เริ่มต้น

บางครั้ง เราต้องตรวจสอบแบบฟอร์มที่กรอกไปแล้ว ตะขอแบบกำหนดเองของเรายังไม่รองรับ — มาเริ่มกันเลย!

ค่าฟิลด์เริ่มต้นจะถูกระบุในการกำหนดค่าสำหรับแต่ละฟิลด์ ในคุณสมบัติ initialValue หากไม่ได้ระบุไว้ ค่าเริ่มต้นจะเป็นสตริงว่าง

เราจะสร้างฟังก์ชัน getInitialState ซึ่งจะสร้างสถานะเริ่มต้นของตัวลดขนาดให้เรา

 function getInitialState(config) { if (typeof config === 'function') { config = config({}); } const initialValues = {}; const initialBlurred = {}; for (let fieldName in config.fields) { initialValues[fieldName] = config.fields[fieldName].initialValue || ''; initialBlurred[fieldName] = false; } const initialErrors = validateFields(initialValues, config.fields); return { values: initialValues, errors: initialErrors, blurred: initialBlurred, submitted: false, }; }

เราผ่านทุกฟิลด์ ตรวจสอบว่ามีคุณสมบัติ initialValue หรือไม่ และตั้งค่าเริ่มต้นตามนั้น จากนั้นเราจะเรียกใช้ค่าเริ่มต้นเหล่านั้นผ่านเครื่องมือตรวจสอบและคำนวณข้อผิดพลาดเริ่มต้นด้วย เราคืนค่าออบเจ็กต์สถานะเริ่มต้น ซึ่งสามารถส่งต่อไปยังฮุ useReducer ของเราได้

เนื่องจากเรากำลังแนะนำอุปกรณ์ประกอบฉากที่ไม่ตรวจสอบความถูกต้องในการกำหนดค่าฟิลด์ เราจึงต้องข้ามไปเมื่อเราตรวจสอบฟิลด์ของเรา ในการทำเช่นนั้น เราเปลี่ยนฟังก์ชัน validateField ของเรา:

 function validateField(fieldValue = '', fieldConfig) { const specialProps = ['initialValue']; for (let validatorName in fieldConfig) { if (specialProps.includes(validatorName)) { continue; } // as before } }

ขณะที่เราเพิ่มคุณสมบัติเช่นนี้ต่อไป เราก็สามารถเพิ่มคุณสมบัติเหล่านี้ในอาร์เรย์ specialProps ของเราได้

  • ดูการสาธิต CodeSandbox

สรุป

เรากำลังดำเนินการสร้างไลบรารีตรวจสอบความถูกต้องที่น่าทึ่ง เราได้เพิ่มคุณสมบัติมากมาย และตอนนี้เราค่อนข้างเป็นผู้นำที่มีความคิดดี

ในส่วนถัดไปของชุดนี้ เราจะเพิ่มคุณสมบัติพิเศษทั้งหมดที่ทำให้ห้องสมุดการตรวจสอบของเรามีแนวโน้มใน LinkedIn