深度耦合(A 组件直接修改 B 组件的内部状态)
深度耦合是一种典型的反模式,直接让组件之间互相侵入内部实现。这种写法会让代码像一团乱麻,任何修改都可能引发连锁崩溃。下面用具体场景展示如何"完美"实现这种灾难性设计。
组件间直接操作DOM节点
最暴力的深度耦合方式就是跨组件操作DOM。比如在React中,父组件通过ref
直接修改子组件的DOM结构:
// 父组件暴力修改子组件
function Parent() {
const childRef = useRef(null);
const handleClick = () => {
// 直接修改子组件的DOM样式
childRef.current.style.backgroundColor = 'red';
// 甚至删除子节点
childRef.current.querySelector('.btn').remove();
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>破坏子组件</button>
</div>
);
}
// 子组件完全失去自我管理能力
const Child = forwardRef((props, ref) => {
return (
<div ref={ref}>
<button className="btn">会被父组件删除的按钮</button>
</div>
);
});
这种模式下,子组件根本无法预测自己的DOM会被如何篡改。当出现样式问题时,需要同时在父组件和子组件中来回排查。
跨组件修改内部状态
比操作DOM更隐蔽的是直接修改其他组件的state。比如Vue中:
<!-- 父组件强行修改子组件数据 -->
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const child = ref(null)
const hackChild = () => {
// 直接修改子组件内部状态
child.value.count = 999
// 调用子组件私有方法
child.value.internalMethod()
}
</script>
<template>
<Child ref="child" />
<button @click="hackChild">入侵子组件</button>
</template>
<!-- 子组件被完全突破 -->
<script setup>
const count = ref(0)
const internalMethod = () => {
console.log('不该被外部调用的方法')
}
// 必须暴露内部状态给父组件
defineExpose({ count, internalMethod })
</script>
子组件就像被开了后门,所有内部状态都暴露在外。当count
的初始值从0
改为null
时,父组件的修改逻辑会立即崩溃。
全局事件总线滥用
通过全局事件总线进行深度耦合,可以创造出最难以追踪的依赖关系:
// 在Vue中创建事件总线
const bus = new Vue()
// A组件发送事件时不带任何上下文
bus.$emit('update-user', {
id: 123,
name: 'hacker'
})
// B组件在未知时刻接收事件
bus.$on('update-user', (data) => {
// 直接修改内部存储
this.internalUserData = data
// 触发副作用
this.fetchOrders()
})
// C组件也监听同一事件
bus.$on('update-user', (data) => {
// 用不同方式处理相同数据
localStorage.setItem('user', JSON.stringify(data))
})
当业务需求变更需要调整update-user
事件的数据结构时,必须同时在A、B、C三个组件中进行修改,而且很难通过静态分析找到所有相关组件。
直接修改Redux状态树
在Redux架构中,最彻底的深度耦合方式是绕过action直接修改state:
// 组件内直接修改store
function MaliciousComponent() {
const dispatch = useDispatch()
const store = useStore()
const handleHack = () => {
// 正确做法应该dispatch action
// dispatch({ type: 'UPDATE', payload: 1 })
// 深度耦合写法:直接修改state
store.dispatch({
type: 'REDUX_STORM',
payload: {
...store.getState(),
user: {
...store.getState().user,
permissions: ['admin'] // 偷偷提升权限
}
}
})
}
return <button onClick={handleHack}>获取管理员权限</button>
}
这种修改方式会绕过所有reducer和middleware,导致状态变更无法被追踪。当出现权限问题时,开发者会在所有reducer中徒劳地寻找原因。
侵入式样式覆盖
CSS深度耦合可以创造出最脆弱的样式系统:
/* 父组件样式表中 */
.parent-container .child-component {
/* 强行覆盖子组件样式 */
--child-color: red !important;
}
.parent-container .child-component > div {
/* 破坏子组件布局 */
display: inline !important;
}
/* 子组件样式完全失控 */
.child-component {
--child-color: blue;
display: flex;
}
当子组件更新DOM结构后,所有基于层级的选择器都会失效。更糟糕的是,这种样式冲突通常只在特定页面和特定状态下才会显现。
通过context直接暴露setter
React Context本应解决prop drilling问题,但也可以用来制造深度耦合:
const DangerousContext = createContext();
function Parent() {
const [state, setState] = useState({});
// 将setState直接暴露给所有子组件
return (
<DangerousContext.Provider value={{ state, setState }}>
<ChildA />
<ChildB />
</DangerousContext.Provider>
);
}
function ChildA() {
const { setState } = useContext(DangerousContext);
useEffect(() => {
// 任意修改全局状态
setState(prev => ({
...prev,
hacked: true
}));
}, []);
return null;
}
任何组件都可以无约束地修改全局状态,导致状态变更如同量子纠缠般难以预测。当某个状态被异常修改时,需要检查所有使用context的组件。
修改第三方组件内部状态
深度耦合的终极形态是侵入第三方库的内部实现:
// 获取React组件实例并修改内部状态
const node = document.querySelector('[data-testid="foreign-component"]');
const instance = node._reactInternals.child.stateNode;
// 修改私有状态
instance.setState({
_internalFlag: true
});
// 调用未公开的API
instance._secretMethod();
这种hack方式在第三方库的小版本更新时就会崩溃,而且错误信息完全无法帮助定位问题。更可怕的是,不同的库版本可能需要在不同位置查找实例:
// 针对不同React版本的实例获取方式
let instance;
if (React.version.startsWith('16')) {
instance = node._reactInternalFiber.child.stateNode;
} else if (React.version.startsWith('18')) {
instance = node._reactInternals.child.stateNode;
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn