01|认识 React:如何创建你的第一个 React 应用?
- 组件
- 内置组件:映射到HTML的节点组件
- 自定义组件:自己创建的组件
- 状态
- state:管理组件内部状态
- props:组件外部传入的状态
- JSX
- 语法糖(JSX 不是一种新的概念,只是原生JS的另外一种写法)
```jsx
function Counter() {
const [count, setCount] = React.useState(0);
return (
- 语法糖(JSX 不是一种新的概念,只是原生JS的另外一种写法)
```jsx
function Counter() {
const [count, setCount] = React.useState(0);
return (
React.createElement( “div”, null, React.createElement( “button”, { onClick: function onClick(){ return setCount(count + 1); } }, React.createElement(CountLabel, {count: count}) ) )
<a name="pcY3c"></a>
# 02|理解 Hooks:React 为什么要发明 Hooks?
<a name="qHOKc"></a>
### 类组件 VS 函数组件
使用类组件的弊端(class的优势没有发挥的空间)
- 组件之间不会相互集成
- 组件的方法不会被外部调用
函数组件<br />UI = render(state)
<a name="GJo0e"></a>
### Hooks
> hooks解决的本质问题,就是跟纯函数组件提供状态管理的能力,通过提供外部的数据源,当数据发生变化时,组件渲染,但是在多次渲染期间,可以保持外部数据的状态
如何实现HOOKS?<br />Hooks就是把某个 **目标结果(函数组件)**钩到可能变化的 **数据源或者事件源上(state、url、window size)**,那么当被钩到的数据源或者事件发生变化时,产生目标结果的代码会重新执行,产生更新后的结果(**view视图**)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/152077/1661930704465-77f24462-5950-47f3-b45e-116a2943b5d4.png#clientId=ufba0bf06-af47-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=552&id=u96d46df8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=552&originWidth=1110&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33378&status=done&style=none&taskId=u762bc9ac-4a30-40f1-85d3-ec69403c8e7&title=&width=1110)
Hooks的优势
- 简化逻辑复用
- HOC
- render props
- 有助于关注点分离
<a name="ltUPS"></a>
# 03|内置 Hooks(1):如何保存组件状态和使用生命周期?
<a name="hUmMd"></a>
# 04|内置 Hooks(2):为什么要避免重复定义回调函数?
<a name="fcA1W"></a>
### useState
- 定义一个state,并提供一个修改的方法
```javascript
const [count, setCount] = useState(0)
注意事项
- state 不要存放能够通过计算得到的值
useEffect
用于执行一段副作用(一段和当前执行结果无关的代码)
在函数组件当次执行的过程中,useEffect中的代码的执行是不影响渲染出来的UI的
使用场景:
每次render后都执行(不提供第二个依赖项)
useEffect(()=>{
// dosomething
})
仅第一次render后执行(提供一个空的数组)
useEffect(()=>{
// dosomething
}, [])
第一次render后以及依赖项发生变化后执行(提供依赖数组)
useEffect(()=>{
// dosomething
}, [deps])
组件unmount后执行
useEffect(()=>{
return ()=>{}
},[])
useCallback
缓存回调函数
useCallback(fn, deps)
useMemo
缓存计算的结果
useMemo(fn, deps)
useRef
- 用于在多次渲染之间共享数据
- 活动元素的真实DOM(input 获得焦点)
const refContainer = useRef(initVal)
useContext
让react 函数组件具备了定义全局的响应式数据的能力
创建一个 context管理文件 并导出该上下文 CounterContext
import { createContext } from 'react';
export const CounterContext = createContext();
父组件通过value提供传递下去的数据,并包裹子组件
<CounterContext.Provider value={{count, setCount}}>
<Counter/>
</CounterContext.Provider>
子组建消费父组件传递的数据
const value =useContext(CounterContext);
05|进一步认识 Hooks :如何正确理解函数组件的生命周期?
类组件 VS 函数组件
类组件:在某个生命周期我要做什么
函数组件:当某个状态发生变化时,我要做什么
构造函数 constructor
类组件:在其他代码执行之前的一次性初始化工作
函数组件:一次性的代码执行
import {useRef} from 'react';
function useSingleton(callback){
const called = useRef(false);
if(called.current){
return;
}
callback();
called.current = true;
}
// demo
import useSingleton from './useSingleton';
const MyComp = () => {
// 使用自定义 Hook
useSingleton(() => {
console.log('这段代码只执行一次');
});
return (
<div>My Component</div>
);
};
三种常用的生命周期方法
useEffect(() => {
// componentDidMount + componentDidUpdate
console.log('这里基本等价于 componentDidMount + componentDidUpdate');
return () => {
// componentWillUnmount
console.log('这里基本等价于 componentWillUnmount');
}
}, [deps])
Class 组件中还有其它一些比较少用的方法,比如 getSnapshotBeforeUpdate, componentDidCatch, getDerivedStateFromError 目前在hooks还无法实现
06|自定义Hooks :四个典型的使用场景
抽取业务逻辑
import { useCallback, useState } from 'react';
const useCounter = (step) =>{
const [counter, setCounter] = useState(0);
// 加一
const increment = useCallback(()=>{
setCounter(counter + step);
}, [counter, step])
// 减一
const reducer = useCallback(()=>{
setCounter(counter - step);
}, [counter, step])
return {counter, increment, reducer}
}
export default useCounter;
import {useState} from 'react';
import useCounter from './useCounter';
const Counter = ()=>{
const [setp, setStep] = useState(1);
const {counter, increment, reducer} = useCounter(setp);
return <div>
{counter}
<button onClick={increment}>add</button>
<button onClick={reducer}>reducer</button>
<button onClick={()=>{
setStep(Math.floor(Math.random() * 10))
}}>change setp {setp}</button>
</div>
}
export default Counter;
封装通用逻辑
利用了 Hooks 能够管理 React 组件状态的能力,将一个组件中的某一部分状态独立出来,从而实现了通用逻辑的重用
import { useCallback } from 'react';
import {useState} from 'react';
const useAsync = (execFun)=>{
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const fetchFn = useCallback(()=>{
setLoading(true);
setError(null);
return execFun().then((res)=>{
setData(res);
}).catch((err)=>{
setError(err);
}).finally(()=>{
setLoading(false);
})
}, [execFun]);
return {data, error, loading, fetchFn};
}
export default useAsync;
const fetchUsers = async ()=>{
const res = await fetch("https://reqres.in/api/users/");
const json = await res.json();
return json.data;
}
const {data, error, loading, fetchFn} = useAsync(fetchUsers);
监听浏览器状态
可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新
import { useEffect, useState } from 'react';
const getScrollPosition = ()=>{
return {
x: document.documentElement.scrollLeft || document.body.scrollLeft,
y: document.documentElement.scrollTop || document.body.scrollLeft,
}
}
const useScroll = ()=>{
const [position, setPosition] = useState(getScrollPosition());
useEffect(()=>{
const handler = ()=>{
setPosition(getScrollPosition(document));
}
document.addEventListener('scroll', handler)
return ()=>{
document.body.removeEventListener('scroll', handler)
}
}, [])
return position;
}
export default useScroll;
拆分复杂组件
07|全局状态管理:如何在函数组件中使用 Redux?
redux的三个概念
- store
- reducer
- action
异步Action
使用模式,通过组合同步的Action,用一致的方案提供了处理异步逻辑的方案
Redux提供了一个middleware的机制实现异步的action
middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用。在这个拦截器中,你可以自由处理获得的 action。无论是把这个 action 直接传递到 reducer,或者构建新的 action 发送到 reducer,都是可以的。
redux-thunk使用
Redux中的Action不仅可以是一个Object,也可以是一个函数;如果发现接受的action是函数的时候,那么不会传递给Reducer,而是先执行这个函数,并把dispatch作为参数传递给这个函数,从而在函数中可以自由的决定何时、如何的发送action
- 创建 Redux Store 时指定了 redux-thunk ```javascript mport { createStore, applyMiddleware } from ‘redux’ import thunkMiddleware from ‘redux-thunk’ import rootReducer from ‘./reducer’
const composedEnhancer = applyMiddleware(thunkMiddleware) const store = createStore(rootReducer, composedEnhancer)
2. dispatch action 时就可以 dispatch 一个函数用于来发送请求
```javascript
function fetchData() {
return dispatch => {
dispatch({ type: 'FETCH_DATA_BEGIN' });
fetch('/some-url').then(res => {
dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
}).catch(err => {
dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
})
}
}
- dispatch action 时就可以 dispatch 一个函数用于来发送请求 ```javascript import fetchData from ‘./fetchData’;
function DataList() { const dispatch = useDispatch(); // dispatch 了一个函数由 redux-thunk 中间件去执行 dispatch(fetchData()); } ```