古法编程: React思维模型快速建立
![]()
使用React的方法论与思想,方便我这个古法编程者Vibe Coding的时候更能游刃有余。
从原始操作DOM API,编写每个操作步骤的命令式,到使用React负责处理DOM的细节,程序员只描述最终结果的声明式编程,最终实现DOM响应数据的变化而自发做出更改的响应式。其中声明式编程,以现代的我的视角了看很像Vibe Coding,我们只需要描述需求即可。
一层一层的抽象出来。
命令式->声明式->Vibe Coding(氛围编程)
React思维模型
HTML上凿洞,动态数据露脸
就像旅游景区镂空的拍照墙,我们只需要露个头,一个完美的姿势拍照就好了。
import { useState } from "react";
export default function App() {
let [who, setWho] = useState("Pkmer");
return (
<div className="icons">💪{who}👊</div>
);
}
JSX是伪装成HTML的JavaScript代码
React开发工具将JSX标签自动转化为相应的JavaScript代码
function App(){
let [username] = userState(() => "Pkmer")
return <div className="container">
<text className="name">💪{username}👊</text>
<button />
<div>
}
转化成的JavaScript
import {jsx as _jsx} from "react"
function App(){
let [username] = userState(() => "Pkmer")
return _jsx("div",{
className: "container",
children: [
_jsx("text",{className: "name",children: ["💪",username,"👊"]}),
_jsx("button")
]
})
}
一次组件渲染,一页手翻书
组件每次渲染的时候,都会重新执行一次
function ComponentXxx(){
console.log("run run run...")
return // ...jsx...
}
不可变特性(快照)
组件的state数据是不可变的,每一次都只是一个快照,要想更新数据,需要推倒重建。一个组件就像一座大厦一样,就算只给窗户换一种颜色,那么这个大厦就得重建(重新渲染)
下面的代码第一种错误的示范中:在React看来house还是原来的那个house,同一个引用,React会不会重新渲染。第二种正确的方式: house已经不再是原来的house了,尽管大部分内容是一样的,但是它已经是一个新的house.
const [house,setHouse] = useState({windowColor: "蓝色",floors: 2});
// ❌️这样修改不会有效果,house只是快照
house.windowColor = "白色"
setHouse(house)
// 正确✅️
const newHouse = [...hourse,windowColor: "白色"]
setHouse(newHouse)
可以这样理解:React为了性能考虑,没有进行深层比较,这里只是浅层的比较,发现house的引用变了,才更新。
组件的动态组合方式:children
相比搭积木一样一个一个组件固定的组合方式,children这个特殊的属性,提供了动态组件组合方式
function Dialog({children}){
return <div>
<div>{children}</div>
</div>
}
// 使用
function App(){
return <Dialog>
<Heading>I Love Coding</Heading>
<Slogon>此刻我在深圳图书馆-北馆,充电学习</Slogon>
</Dialog>
}
传送工程师的接力:单向数据流
props层层传递
数据所有者要想将数据传递给消费者,需要进行层层传递,尽管中间传递者并不消费这个数据
![]()
context电梯,按需取货
在提供数据的楼层(上层)将包裹(数据),放入电梯(context)。电梯往下走,下面的哪个楼层需要这个包裹(数据),自己在对应的楼层打开电梯取走。
const ThemeContext = React.createContext("light")
// 上层数据所有者,提供数据,放入电梯
function Home(){
const [theme,setTheme] = useState("light")
return <ThemeContext.Provider value={theme}>
<Page />
</ThemeContext.Provider>
}
App->Page->Header->Logo,中间层Page,Header都不需要数据,只有Logo需要。
function Page(){
return <div>
<Header />
<Content />
<Footer />
</div>
}
function Header(){
return <div>
<Logo />
<Title />
</div>
}
function Logo(){
// 在我这层,打开电梯取出包裹(数据)
const theme = useContext(ThemContext)
return theme === "dark" ? <DarkLogo /> : <LightLogo />
}
便携式虫洞
由于数据是单向传递的,如果子组件要想改变数据,需要数据提供层进行修改。想要修改上层数据,上层提供则需要将权力下放。而这个下放的过程,就像虫洞一样,子组件员工可以将手伸向上层老板的办公室,直接进行签合同。
function BossOffice() {
const [contract, setContract] = useState('初始合同');
// 老板下放修改权限的方法
const signContract = (newContract) => {
setContract(newContract);
};
return (
<div>
<h1>老板办公室 - 当前合同: {contract}</h1>
{/* 将修改权限通过 props 下放给子组件 */}
<EmployeePortal onSignContract={signContract} />
</div>
);
}
function EmployeePortal({ onSignContract }) {
const handleSign = () => {
// 子组件员工直接调用上层传来的方法,就像穿过虫洞伸手改数据
onSignContract('员工新签的合同');
};
return (
<div>
<h2>员工虫洞通道</h2>
<button onClick={handleSign}>伸向老板办公室签合同</button>
</div>
);
}
Hook勾子将数据放入React大海又勾回来
Hook将函数组件内的数据保存到外部环境,以备下次渲染所用。
- 保存只读数据: useMemo(保存函数的返回值),useCallback(保存的是回调函数本身)
- 保存可变数据,更改时触发渲染: useState和useReducer(更底层)
- 保存可变数据,更改时不触发渲染: useRef
useEffect与生命周期回调方法
useEffect完全可以代替类组件中的三个生命周期回调方法
class Xxx extends Component{
// 挂载以后运行
componentDidMount{}
// 每次更新以后运行
componentDidUpdate(prevProps){}
// 将要卸载前运行
componentWillUnmount(){}
}
useEffect对应的行为方式
useEffect(() => {
// 数组参数为空,只在组件第一次渲染时调用
},[])
useEffect(() => {
// 当数组中的元素变化更新时会执行
},[要变化的值,或者函数]) // 要诚实的告诉哪些值会变。
useEffect(() => {
// 省略数组。将在组件每次渲染后运行此处的代码
})
useEffect的真正职责:管理组件副作用
药物的副作用并不是药物的目的,当然是越少越好。而程序里的副作用却是我们有意而为,是程序的功能之一
所谓副作用,是函数组件与其周边环境发生了交互的额外任务,比如操作window对象,访问网络请求后端api,读取本地文件等,这些作用都超出了当前函数组件的范围。函数组件关心的是state和props
function Boat(props){
useEffect(() => {
const listener = ...
window.addEventListener('keydown',listener)
...
return () => {
window.removeListener('keydown',listener)
}
},[])
}