jon*_*bbs 48 javascript reactjs react-hooks
我第一次尝试React钩子,所有看起来都很好,直到我意识到当我获取数据并更新两个不同的状态变量(数据和加载标志)时,我的组件(数据表)呈现两次,即使两个调用状态更新程序正在发生在同一个函数中.这是我的api函数,它将两个变量都返回给我的组件.
const getData = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(async () => {
const test = await api.get('/people')
if(test.ok){
setLoading(false);
setData(test.data.results);
}
}, []);
return { data, loading };
};
Run Code Online (Sandbox Code Playgroud)
在普通的类组件中,你只需要调用一次来更新可以是一个复杂对象的状态,但是"钩子方式"似乎是将状态分成更小的单元,其副作用似乎是多个在单独更新时呈现.任何想法如何缓解这个?
Yan*_*Tay 50
您可以将loading状态和data状态组合成一个状态对象,然后您可以进行一次setState调用,并且只有一个渲染.
注意:与setState类组件不同,setState返回的from useState不会合并具有现有状态的对象,它会完全替换该对象.如果要进行合并,则需要读取先前的状态并自行将其与新值合并.请参阅文档.
在确定您遇到性能问题之前,我不会过分担心渲染渲染.渲染(在React上下文中)并将虚拟DOM更新提交到真实DOM是不同的事情.这里的渲染是指生成虚拟DOM,而不是更新浏览器DOM.React可以批量setState调用并使用最终的新状态更新浏览器DOM.
const {useState, useEffect} = React;
function App() {
const [userRequest, setUserRequest] = useState({
loading: false,
user: null,
});
useEffect(() => {
// Note that this replaces the entire object and deletes user key!
setUserRequest({ loading: true });
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUserRequest({
loading: false,
user: data.results[0],
});
});
}, []);
const { loading, user } = userRequest;
return (
<div>
{loading && 'Loading...'}
{user && user.name.first}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
const {useState, useEffect} = React;
function useMergeState(initialState) {
const [state, setState] = useState(initialState);
const setMergedState = newState =>
setState(prevState => Object.assign({}, prevState, newState)
);
return [state, setMergedState];
}
function App() {
const [userRequest, setUserRequest] = useMergeState({
loading: false,
user: null,
});
useEffect(() => {
setUserRequest({ loading: true });
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUserRequest({
loading: false,
user: data.results[0],
});
});
}, []);
const { loading, user } = userRequest;
return (
<div>
{loading && 'Loading...'}
{user && user.name.first}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
Vah*_* Al 16
这也有使用的另一个解决方案useReducer!首先,我们定义新的setState。
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{loading: true, data: null, something: ''}
)
Run Code Online (Sandbox Code Playgroud)
之后,我们可以像使用好旧类一样简单地使用它this.setState,而无需使用this!
setState({loading: false, data: test.data.results})
Run Code Online (Sandbox Code Playgroud)
您可能会在我们的新产品中注意到setState(就像以前使用一样this.setState),我们不需要一起更新所有状态!例如,我可以像这样更改我们的一种状态(它不会更改其他状态!):
setState({loading: false})
Run Code Online (Sandbox Code Playgroud)
太好了,哈?!
因此,让我们将所有内容放在一起:
import {useReducer} from 'react'
const getData = url => {
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{loading: true, data: null}
)
useEffect(async () => {
const test = await api.get('/people')
if(test.ok){
setState({loading: false, data: test.data.results})
}
}, [])
return state
}
Run Code Online (Sandbox Code Playgroud)
Meh*_*ani 11
这将:
const [state, setState] = useState({ username: '', password: ''});
// later
setState({
...state,
username: 'John'
});
Run Code Online (Sandbox Code Playgroud)
Sur*_*raj 10
react-hooks中的批处理更新 https://github.com/facebook/react/issues/14259
如果从基于React的事件中触发状态更新(例如按钮单击或输入更改),React当前将批处理状态更新。如果更新是在React事件处理程序之外触发的,例如异步调用,它将不会批量更新。
要复制this.setState从类零件的合并行为,作出反应的文档推荐使用的函数形式useState与传播对象-无需为useReducer:
setState(prevState => {
return {...prevState, loading, data};
});
Run Code Online (Sandbox Code Playgroud)
这两个状态现在合并为一个状态,这将为您节省渲染周期。
一个状态对象还有另一个优点:loading并且data是依赖状态。当状态放在一起时,无效的状态更改变得更加明显:
setState({ loading: true, data }); // ups... loading, but we already set data
Run Code Online (Sandbox Code Playgroud)
你甚至可以更好地确保一致的状态 通过1)使状态- ,,等-明确使用你的状态和2)在减速封装状态逻辑:loadingsuccesserroruseReducer
const useData = () => {
const [state, dispatch] = useReducer(reducer, /*...*/);
useEffect(() => {
api.get('/people').then(test => {
if (test.ok) dispatch(["success", test.data.results]);
});
}, []);
};
const reducer = (state, [status, payload]) => {
if (status === "success") return { ...state, data: payload, status };
// keep state consistent, e.g. reset data, if loading
else if (status === "loading") return { ...state, data: undefined, status };
return state;
};
const App = () => {
const { data, status } = useData();
return status === "loading" ? <div> Loading... </div> : (
// success, display data
)
}
Run Code Online (Sandbox Code Playgroud)
setState(prevState => {
return {...prevState, loading, data};
});
Run Code Online (Sandbox Code Playgroud)
setState({ loading: true, data }); // ups... loading, but we already set data
Run Code Online (Sandbox Code Playgroud)
PS:务必请对前缀定制钩用use(useData而不是getData)。还通过回调来useEffect不能是async。
如果您使用第三方挂钩并且无法将状态合并到一个对象或使用useReducer,那么解决方案是使用 :
ReactDOM.unstable_batchedUpdates(() => { ... })
Run Code Online (Sandbox Code Playgroud)
看这个例子
| 归档时间: |
|
| 查看次数: |
23101 次 |
| 最近记录: |