// context.jsconst GrayContext = React.createContext();export default GrayContext;// App.jsimport GrayContext from './context';function App() {console.log('App rerender');const [globalStatus, setGlobalStatus] = useState({});useEffect(() => {console.log('Get GrayState');setTimeout(() => {setGlobalStatus({gray: true});}, 1000);}, []);return (<GrayContext.Provider value={globalStatus}><GrayComponent /><OtherChild /></GrayContext.Provider>);}// GrayComponent/index.jsfunction GrayComponent() {console.log('GrayComponent rerender');const grayState = useContext(GrayContext);return (<div>子节点{grayState.gray && <div>灰度字段</div>}</div>);}// OtherChild/index.jsfunction OtherChild() {console.log('OtherChild rerender');return (<div>其它子节点</div>);}
// GrayState.jsclass GrayState {constructor() {this.observers = [];this.status = {};}attach(func) {if (!this.observers.includes(func)) {this.observers.push(func);}}detach(func) {this.observers = this.observers.filter(observer => observer !== func);}updateStatus(val) {this.status = val;this.trigger();}trigger() {for (let i = 0; i < this.observers.length; i++) {this.observers[i](this.status);}}}export default new GrayState();// App.jsimport GrayState from './GrayState.js';function App() {console.log('App rerender');useEffect(() => {console.log('Get GrayState');setTimeout(() => {const nextStatus = {gray: true,};GrayState.updateStatus(nextStatus);}, 200);}, []);return (<div><GrayComponent /><OtherChild /></div>);}// GrayComponent/index.jsimport GrayState from './GrayState.js'function GrayComponent() {console.log('GrayComponent rerender');const [visible, setVisible] = useState(false);useEffect(() => {const changeVisible = (status) => {setVisible(status.gray);};GrayState.attach(changeVisible);return () => {GrayState.detach(changeVisible);};}, []);return (<div>子节点{visible && <div>灰度字段</div>}</div>);}
// useStatus.jsimport { useState, useEffect } from 'react';import GrayState from './GrayState';function useGray(key) {const [hit, setHit] = useState(false);useEffect(() => {const changeLocalStatus = (status) => {setHit(status[key]);};GrayState.attach(changeLocalStatus);return () => {GrayState.detach(changeLocalStatus);};}, []);return hit;}export default useGray;// GrayComponent/index.jsimport useStatus from './useGray.js'function GrayComponent() {console.log('GrayComponent rerender');const [visible, setVisible] = useGray('gray');return (<div>子节点{visible && <div>灰度字段</div>}</div>);}
function Mode() {/* 普通书写模式 */const [mode, setMode] = useState('order'); // 定义模式状态const changeHandle = useCallback((mode) => { // 模式切换行为if (mode === 'order') {console.log('切换到随机模式');setMode('random');} else if (mode === 'random') {console.log('切换到循环模式');setMode('loop');} else if (mode === 'loop') {console.log('切换到顺序模式');setMode('order');}}, []);return (<div><Button onClick={() => changeHandle(mode)}>切换模式</Button><div>{mode.text}</div></div>);}
function Mode() {/* 普通的状态模式实现 */const [mode, setMode] = useState({});useEffect(() => {const MODE_MAP = {order: {text: 'order',press: () => {console.log('切换到随机模式');setMode(MODE_MAP.random);},},random: {text: 'random',press: () => {console.log('切换到循环模式');setMode(MODE_MAP.loop);},},loop: {text: 'loop',press: () => {console.log('切换到顺序模式');setMode(MODE_MAP.order);},}};setMode(MODE_MAP.order);}, []);return (<div><Button onClick={() => mode.press()}>切换模式</Button><div>{mode.text}</div></div>);}
/* 借助 reducer 更便捷实现状态模式 */const reducer = (state) => {switch(state) {case 'order':console.log('切换到随机模式');return 'random';case 'random':console.log('切换到循环模式');return 'loop';case 'loop':console.log('切换到顺序模式');return 'order';}};function Mode() {const [mode, dispatch] = useReducer(reducer, 'order');return (<div><Button onClick={dispatch}>切换模式</Button><div>{mode.text}</div></div>);}
OS:“如何优雅地封装自定义 Hooks” 是一个很大的话题,这里仅仅抛转引玉讲述几个观点。
// Bad casefunction useTodayFirstOpen() {const [status, setStatus] = useState();const [isTodayFirstOpen, setIsTodayFirstOpen] = useState(false);useEffect(() => {// 获取用户状态const fetchStatus = async () => {const res = await getUserStatus();setStatus(res);};fetchStatus();// 判断今天是否首次打开const value = window.localStorage.getItem('isTodayFirstOpen');if (!value) {setIsTodayFirstOpen(true);} else {const curr = getNowDate();setIsTodayFirstOpen(curr !== value);}}, []);useEffect(() => {if (status <= 0) {// 未打开时进行二次提醒setTimeout(() => {tryToPopConfirm({onConfirm: () => {setStatus(1);updateUserStatus(1);},});}, 300);window.localStorage.setItem('isTodayFirstOpen', Date.now())}}, [status, isTodayFirstOpen]);}
function useTodayFirstOpen() {const [status, setStatus] = useState();const [isTodayFirstOpen, setIsTodayFirstOpen] = useState(false);// ...const updateStatus = async (val) => {const res = await updateUserStatus(val);// dosomething...}return [status, updateStatus];}
function useTodayFirstOpen() {const [status, setStatus] = useState();const [isTodayFirstOpen, setIsTodayFirstOpen] = useState(false);useEffect(() => {// 获取用户状态const fetchStatus = async () => {const res = await getUserStatus();setStatus(res.notice);};fetchStatus();// ...}, []);// ...}
function useThisWeekFirstOpen() {
const [status, setStatus] = useState();
const [isThisWeekFirstOpen, setIsThisWeekFirstOpen] = useState(false);
useEffect(() => {
// 获取用户状态
// ...
// 判断今天是否首次打开
const value = window.locaStorage.getItem('isThisWeekFirstOpen');
if (!value) {
setIsTodayFirstOpen(true);
} else {
const curr = getNowDate();
setIsThisWeekFirstOpen(diffDay(curr, value) >= 7);
}
}, []);
// ...
}
// 用户状态管理function useUserStatus() {const [status, setStatus] = useState();const fetchStatus = async () => {const res = await getUserStatus();setStatus(res);};useEffect(() => {fetchStatus();}, []);const updateStatus = useCallback(async (type, val) => {const res = await updateUserStatus(type, val);if (res) {console.log('设置成功');fetchStatus();} else {console.log('设置失败');}}, []);return [status, updateStatus];}// 二次提醒function useSecondConfirm(key, gapDay, confirmOptions = {}) {const [isConfirm, setIsConfirm] = useState(false);const showConfirm = useCallback(() => {const curr = Date.now();const lastDay = window.localStorage.getItem(`${key}_lastCheckDay`);if (!lastDay || diffDay(curr, lastDay) > gapDay) {setTimeout(async () => {tryToPopConfirm({title: confirmOptions.title,content: confirmOptions.content,onConfirm: () => setIsConfirm(true),});}, 300);window.localStorage.setItem(`${key}_lastCheckDay`, curr);}}, [gapDay]);return [isConfirm, showConfirm];}function useStatusWithSecondConfirm(type, gapDay, confirmOptions) {const [userStatus, setUserStatus] = useUserStatus();const [isConfirm, showConfirm] = useSecondConfirm(type, gapDay, confirmOptions);// 关闭状态二次提醒用户是否打开useEffect(() => {console.log(userStatus);if (userStatus && userStatus[type] <= 0) {showConfirm();}}, [userStatus]);// 确认后修改用户状态useEffect(() => {if (isConfirm) {setUserStatus(type, 1);}}, [isConfirm]);return [userStatus ? userStatus[type] : null, setUserStatus];}// 使用时function Component() {const [status, setStatus] = useStatusWithSecondConfirm('notice',1,{title: '是否打开提醒',content: '打开通知以避免错过重要信息'});return (<><label>打开消息提醒</label><Switchchecked={status}onChange={setStatus}/></>);}
第 n + 1 要义努力探索中……
点击阅读原文参加乘风者计划~