// context.js
const GrayContext = React.createContext();
export default GrayContext;
// App.js
import 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.js
function GrayComponent() {
console.log('GrayComponent rerender');
const grayState = useContext(GrayContext);
return (
<div>
子节点
{grayState.gray && <div>灰度字段</div>}
</div>
);
}
// OtherChild/index.js
function OtherChild() {
console.log('OtherChild rerender');
return (
<div>其它子节点</div>
);
}
// GrayState.js
class 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.js
import 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.js
import 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.js
import { 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.js
import 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 case
function 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>
<Switch
checked={status}
onChange={setStatus}
/>
</>
);
}
第 n + 1 要义努力探索中……
点击阅读原文参加乘风者计划~