換了工做的新環境後,感觸仍是蠻深的,能感覺到的是不少人對待工做的極致,相比以前而言,會更加的適合對工做充滿激情的人 🤔;一個最大的挑戰就是技術棧了,從本身熟練的Vue轉換到React,快速的學習和上手,坑天然也是踩了很多;html
在實際項目中,咱們會將多一個完整的功能拆分紅多個模塊,用來處理邏輯,中臺開發中,最多見的莫過於「搜索列表」+「添加數據」等操做;react
簡單描述下交互git
需求、交互都很普通,可是正是如此,我使用useCallback
掉入了大坑github
const OperationList: React.FC = () => {
const [showCreateGood, setShowCreateGood] = useState(false);
const [searchQuery, setSearchQuery] = useState<ListQueryParams>(initOpQuery);
const initFetchData = async (query?: ListQueryParams) => {
const searchParams = query ?? searchQuery;
// 執行請求操做 省略
};
useEffect(() => {
initFetchData(searchQuery);
}, []);
// 搜索內容
const onSearch = (value?: ListQueryParams) =>
new Promise((resolve, reject) => {
setSearchQuery(value);
initFetchData(value)
.then(res => resolve(res))
.catch(error => reject(new Error(error)));
});
return (
<Card> //...... <Button onClick={onSearch}></Button> {<CreateGoodsSold visible={showCreateGood} setVisible={setShowCreateGood} createSuccess={initFetchData} />} </Card>
);
};
複製代碼
// eslint-disable-next-line max-lines-per-function
const CreateGoodsSoldOut: React.FC<CreateGoodsSoldOutProps> = ({ visible, setVisible, createSuccess }) => {
const [form] = Form.useForm();
// 提交站內信內容
const onSubmit = async (extraParams = { flag: 0 }) => {
let postParams: postCreateNotice = form.getFieldsValue();
try {
await createSoldOut({ ...postParams, ...extraParams }); ;
//添加成功 調用父組件的方法
createSuccess();
} catch (error) {
}
};
// 表單校驗完成 + 彈框提示
const onfinish =useCallback( () => {
Modal.confirm({
title: '',
content: 'ccc?',
icon: null,
okText: '肯定提交',
cancelText: '取消',
centered: true,
onOk: () => {
onSubmit();
},
},[])
return (
<Drawer destroyOnClose forceRender width={700} visible={visible} onCancel={clearForm} onOk={() => { form.submit(); }} okButtonProps={{ htmlType: 'submit' }} > // ......表單收集項目 </Drawer>
);
};
複製代碼
內容嵌套有點亂?? 來張圖數組
這看似普通的代碼 在一次次的進行校驗後居然出現了問題緩存
searchQuery
是保存當前搜索數據的searchQuery
在請求函數中始終不是當前最新的數據內部的函數的searchQuery
和外部的searchQuery
是不一樣步的,也就是函數內部沒有取到最新的值性能優化
咱們梳理邏輯,父組件定義了一個函數initFetchData
傳給子組件,子組件在onSubmit 中調用了這個函數,可是 在onfinish
中咱們使用了useCallback
,此時useCallback
傳遞的第二個數組是空,也就是不依賴的,只在初始渲染時候進行定義,後面任何值變化時候都不會引發這個函數變化,爲此產生了疑問? useCallback
定義的無依賴的函數,對於內部所調用的函數的值是否有所影響,也就是initFetchData
中調用的是初始保留的值? 爲此 進行了一番探究markdown
根據上述問題的疑問,進行demo的測試,咱們疑問點在於:閉包
useCallback
包裝的函數調用父組件函數時候,父組件函數內部的數值獲取,即被useCallback
包裹的函數,內部函數調用的做用域;// 父組件的定義
const UseCallBackDemo = () =>{
const [query,setQuery] = useState(null)
const parentFun = (value)=>{
console.log(value,query)
}
const changeQuery = () =>{
setQuery(222)
}
return(<> 測試useCallBack <button onClick={changeQuery}>更改query的值{query}</button> <Children1 parentsMethod={parentFun}></Children1> <Children2 parentsMethod={parentFun}></Children2> </>)
}
複製代碼
clickParent
未被useCallback
包裹clickParent
被useCallback
包裹// 兩個子組件
const Children1 =memo( ({ parentsMethod })=>{
const clickParent = ()=>{
parentsMethod("子組件1調用了");
}
return(<div> 我是子組件1 <button onClick={clickParent}>我是子組件1 調用parentsMethod</button> </div>)
})
const Children2 =memo( ({ parentsMethod })=>{
//
const clickParent = useCallback(()=>{
parentsMethod("子組件2調用了 useCallback");
},[])
return(<div> 我是子組件2 <button onClick={clickParent}>我是子組件2 調用parentsMethod</button> </div>)
})
複製代碼
點擊按鈕後async
子組件2中使用了useCallback的無依賴函數,調用父組件時候,query仍是初始的值,並未獲得更新;
<Children2 parentsMethod={parentFun} query={queru}></Children2>
const Children2 =memo( ({ parentsMethod,query })=>{
const clickParent = useCallback(()=>{
parentsMethod("子組件2調用了 useCallback");
},[query])
return(<div> 我是子組件2 <button onClick={clickParent}>我是子組件2 調用parentsMethod</button> </div>)
})
複製代碼
使用了useCallback
進行包裝的函數,會影響到其內部的全部調用函數 帶着這個猜測進行debugger
查看函數上下文和執行棧
當執行到Children2
的函數時候,此時parentsMethod
的上下文和做用域以下;此時parentsMethods
的做用域上是產生了一個閉包,也就是定義的query
的初始值;
執行到父組件函數內部
也會存在閉包,做用域使用的是範圍是最新的
執行到parents的時候
````useCallback```優化性能,可是使用可能致使出現錯誤,影響內部調用的函數做用域,所以謹慎使用,若是存在依賴函數,必定要進行相關依賴函數的監聽;
useCallback 的做用在於利用 memoize 減小無效的 re-render,來達到性能優化的做用,callback 內部對 state 的訪問依賴於 JavaScript 函數的閉包。若是但願 callback 不變,那麼訪問的以前那個 callback 函數閉包中的 state 會永遠是當時的值。
useCallback
的實現有中,分爲mountHook
和updateHook
;
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 利用memoizedState 緩存mount階段時候的變量
hook.memoizedState = [callback, nextDeps];
return callback;
}
複製代碼
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
// 獲取下一個nextDeps
const nextDeps = deps === undefined ? null : deps;
// 獲取前一個存儲的內部數值
const prevState = hook.memoizedState;
// 若是前一個不是空的 則進行淺比較
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
// 存儲當前的這個內容
hook.memoizedState = [callback, nextDeps];
return callback;
}
複製代碼