写在前面:学习 React
要时刻告诉自己就是在写 JavaScript
函数组件的创建方式
// 箭头函数形式
const Hello = (props) => { return <div>{props.message}</div> }
// function 形式
function Hello(props) {
return <div>{props.message}</div>
}
就是如此,和 JS 函数的创建一样(心里默念学习 React
要时刻告诉自己就是在写 JavaScript
)
Props 外部数据
就是函数组件的参数
State 和生命周期
没有
那我走? 🤷♀️
别急,React v16.8.0 推出 Hooks API,可厉害了,了解下吧~
Hooks
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数,使用它们有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
useState
还记得在 Class 组件中怎么使用 state
的吗
看这个例子 🌰
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
我们通过在构造函数中设置 state 初始值为 { count: 0 } ,当用户点击按钮后,我们通过调用 this.setState() 来增加 state.count。
回顾一下上面函数组件的创建方式,我们似乎无法很方便地向里面添加一些 state 使得 state 变化时能自动更新 UI
这时就该主角 useState
登场啦 😎
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
使用 useState 定义 state 变量返回的是一个有两个值的数组。第一个值是当前的 state,第二个值是更新 state 的函数。
useState() 方法里面唯一的参数就是初始 state。这里声明了一个叫 count 的 state 变量,然后把它设为 0。
通过调用 setCount 来更新当前的 count。
注意事项:
- 如果 state 是一个对象,不能部分 setState,因为 setState 不会合并属性
- 对于 setState(obj),如果 obj 地址不变,那么 React 就认为数据没有变化
useEffect
useEffect 给函数组件增加了操作副作用的能力。它跟 Class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
来看下 useEffect 的用法
// 模拟 componentDidMount,注意第二个参数
useEffect(() => { /* 只在第一次渲染时会执行 */ }, [])
// 模拟 componentDidUpdate,注意第二个参数
useEffect(() => { /* 任意属性更新就会执行,第一次渲染也会 */ })
useEffect(() => { /* 只有n更新才会执行,第一次渲染也会 */ }, [n])
// 模拟 componentWillUnmount,注意第一个参数的 return 语句
useEffect(() => {
return () => { /* 执行组件卸载的时候清除操作(保护环境) */ }
})
如果同时存在多个 useEffect,会按照出现次序执行
useLayoutEffect
在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
特点
- useLayoutEffect 总是比 useEffect 先执行
- useLayoutEffect 里的任务最好是影响了 Layout
App() ---> 执行 ---> VDOM ---> DOM ---> 改变外观 ---> useEffect
↑
useLayoutEffect
useContext
用法
- 声明一个 context 对象
const MyContext = React.createContext()
- 使用 <MyContext.Provider> 圈定作用域
function App(){ return ( <MyContext.Provider value={520}> <Childlren /> </MyContext.Provider> ) }
- 在作用域内使用 useContext(MyContext) 获取上下文
function Chrilren() { const value = useContext(MyContext) return ( <div>{ value }</div> ) }
useReducer
使用官网上的例子
// 第 1 步:创建初始值 initialCount
function init(initialCount) {
return {count: initialCount};
}
// 第 2 步:创建所有操作 reducer(state, action)
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
// 第 3 步:传给 useReducer ,得到读写 API
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
// 第 4 步:调用 dispatch({type: '操作类型'})
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useMemo
使用背景:React 默认有多余的 render
<父组件>
<子组件 />
</父组件>
当 <父组件> 中 state 变化时, <子组件> 跟着重新渲染,但如果 <子组件> 的 props 没有变化是没必要重新渲染的。
用 <子组件2> = React.memo(<子组件>) 可以解决,但是有 Bug :在 <子组件> 内添加了监听函数后就会失效。原因是函数是对象, <父组件> 重新渲染会导致函数地址不同从而使 props 发生变化。
用 useMemo 包装 <子组件> 内的监听函数。
用法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized
值
useCallback
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useRef
const refContainer = useRef(initialValue)
useRef 返回一个可变的 ref
对象,其 .current
属性被初始化为传入的参数(initialValue
)。返回的 ref
对象在组件的整个生命周期内持续存在。
读取方式:refContainer.current
useRef
常用于访问子组件
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素<input>
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
自定义 Hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook,通常用于封装数据操作。
下面是一个自定义 Hook 示例
// useRecords.jsx
import { useState } from 'react';
export const useRecords = () => {
const [records, setRecords] = useState([])
// 一些增删查改的操作
const findRecord = () => {}
const addRecord = () => {}
const deleteRecord = () => {}
const updateRecord = () => {}
return {records, addRecord, deleteRecord, findRecord, updateRecord}
}
使用自定义 Hook
function App() {
const {records} = useRecords()
// 其他代码
}