简介
本文旨在梳理React hooks组件的基本使用及常见API的说明和一些注意事项。部分章节涉及到React的底层工作原理。
React Hooks组件就是函数组件加Hooks API。React是一个通过jsx语法描述UI的javascript前端库。其核心思想是将前端页面组件化,通过组件的组合构成视图,父子组件之间遵循单向数据流原则。在React内部维护组件状态并代理DOM操作,通过修改状态来更新视图。
函数组件基础
函数组件就是一个普通函数,接收父组件传递的props参数,返回jsx描述的UI节点。编译后,jsx被编译成创建虚拟DOM的运行时函数,在react内部通过一些列处理后根据虚拟DOM绘制真实DOM。在挂载和更新阶段都会执行该函数。
jsx
function Hello(props) {
return (
<div>
<h1>hello world</h1>
</div>
)
}
响应式原理
React的响应式是通过数据驱动视图,当数据发生变化时,视图自动更新。
触发React Hooks组件更新的方式有两种。一种是组件setState发生变化;另外一种是组件的父组件更新(这种情况下组件会重新执行,但不一定会重新渲染视图)。
1、React Hooks组件的状态通过useState Hook创建。useState接收一个初始值做为初始状态。返回初始状态和setState,setState接收修改后的值。调用setState,传入修改后的值触发重新渲染流程。注意:state要遵循不可变原则,即不可直接修改state。想要改变state需要将一个新的值传递给setState,并且通过setState修改状态才能触发组件更新。在React内部,setState是异步的,调用后并不会立即执行,react会将修改收集起来统一调度,根据任务的优先级来执行。
2、父组件更新时,所有的子组件都会重新执行,但视图不一定会更新。
基本用法
1、创建React应用。通过createRoot api 创建根节点,createRoot接收rootElement并返回ReactDOMRoot根节点对象,根节点对象提供render和unmount等方法实现组件的渲染和卸载。如下:
jsx
const App = () => {
return (
<div>
<Hello />
</div>
)
}
//创建根节点并渲染App组件
createRoot(document.getElementById('root')).render(<App />)
2、添加状态。上面的 Hello
和 App
组件都是函数组件,或者叫做静态(无状态)组件,因为它只能执行一次渲染,无法执行组件更新。为了让函数组件更够持有状态,并且当状态发生改变时更新。我们需要使用到第一个HOOK------useState
。前文介绍过 useState
的基本用法,这里我们给 Hello
组件增加状态,如下:
jsx
function Hello(props) {
const [text, setText] = useState("hello")
function changeText() {
setText(text => text == 'hello' ? 'hi' : 'hello')
}
return (
<div>
<h1>say {text}</h1>
<button onClick={changeText}>change text</button>
</div>
)
}
3、父子组件通信。父组件传递给子组件的属性,会被包装成为props对象传递给子组件,在子组件中可通过props对象获取或者修改数据。父子组件通信还可用于状态提升------当有两个兄弟组件共享同一个状态时,为了方便管理状态的变化,可将状态提取到最近的公共父组件。
jsx
const App = () => {
const [name, setName] = useState('React')
function changeName() {
setName(name => name == 'React' ? 'Vue' : 'React')
}
return (
<div>
<Hello name={name} changeName={changeName} />
</div>
)
}
function Hello({name, changeName}) {
const [text, setText] = useState("hello")
function changeText() {
setText(text => text == 'hello' ? 'hi' : 'hello')
}
return (
<div>
<h1>say {text}</h1>
<button onClick={changeText}>change text</button>
<h1>say {name}</h1>
<button onClick={changeName}>change Name</button>
</div>
)
}
注意:父子组件通信应遵循单项数据流原则,即子组件内部不可直接修改父组件传递的数据,需要通过调用父组件传递的在父组件中定义的方法来修改,确保数据变化的可预测性。
4、ref引用值。当我们希望组件记住某些信息,但又不想让这些信息触发新的渲染时,选择使用ref。ref通常用于脱围机制,用于记录一些DOM节点信息,可以直接操作DOM元素。通过useRef创建,useRef接收初始值,返回ref对象,通过ref.current获取值。
javascript
function RefCompoment() {
const ref = useRef(null)
function focus() {
ref.current.focus()
}
return (
<div>
<input ref={ref} type="text" />
<button onClick={focus}>focus</button>
</div>
)
}
5、Effect。Effect是一种脱围机制,用于处理一些由视图更新引起的副作用,即让一些副作用与组件状态同步。Effect通过useEffect创建,useEffect接收一个回调函数和依赖数组。依赖数组可以不传(每次组件更新时执行Effect)、空数组(仅在第一次挂载时执行Effect)、数组(数组内的状态改变时执行Effect)。回调函数返回一个清理函数,用于在Effect卸载时清理一些数据,比如清理闭包或事件监听等。
jsx
function EffectCompoment() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `count is ${count}`
}, [count])
return (
<div>
<h1>count is {count}</h1>
<button onClick={() => setCount(count + 1)}>count</button>
</div>
)
}
注意:
- 在Effect内部禁止改变依赖的状态,这样会导致组件循环更新。
- Effect是异步执行的,不会阻塞页面的渲染,在某些情况下可能出现闪屏的现象。如果需要在渲染前执行Effect,可使用
useLayoutEffect
替换,但一般不建议这样操作,除非副作用的优先级非常高。 - 合理使用Effect
以上就是React使用层面最基础也是最核心的内容,React仅给我们提供了一套响应式API及事件处理机制(这里暂不做说明),针对其响应性和渲染流程可能存在的问题提供了解决方案以及能力的增强。所以React应用的构建变得非常灵活。后面的章节将根据响应式原理及渲染流程梳理React提供的解决方案。