以结果为导向,写给刚学完前端三剑客和想要了解 React 框架的小伙伴,使得他们能快速上手(省略了历史以及一些不必要的介绍)。
JSX 内联处理函数
到目前为止,我们拥有的 stories 列表只是一个无状态的变量,可以通过搜索来过滤渲染的列表,但是列表本身保持不变。
如果我们想要加一个【从列表中删除项目】的功能,就要取得列表的控制权,我们可以把列表作为 useState hook 的初始 state(之所以不使用我们自定义的 hook,是因为我们不想每次打开浏览器显示的都是缓存列表),然后向下传递一个回调处理函数。
const initialStories = [ ... ]; ... const App = () => { ... const [stories, setStories] = React.useState(initialStories); const handleRemoveStory = (item) => { const newStories = stories.filter( (story) => item.objectID !== story.objectID ); setStories(newStories); }; ... return ( <> <InputWithLabel id="search" value={searchTerm} onInputChange={handleSearch} isFocused > <strong>Search:</strong> </InputWithLabel> <hr /> <List list={searchedStories} onRemoveItem={handleRemoveStory} /> </> ); }; 复制代码
回调函数接收要删除的 item,然后将返回的 newStories 设置为新的 state。
List 组件会向下传递这个函数给它的子组件:
const List = ({ list, onRemoveItem }) => list.map((item) => ( <Item key={item.objectID} item={item} onRemoveItem={onRemoveItem} /> )); const Item = ({ item, onRemoveItem }) => ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> <span> <button onClick={() => onRemoveItem(item)}>Dismiss</button> </span> </div> ); 复制代码
使用【内联处理函数】,在点击按钮时调用 App 组件里的 onRemoveItem()
触发删除事件。
它与常规的处理函数类似,只是函数体放在了 JSX 中,但由于 JS 逻辑隐藏在 JSX 中,可能会导致代码难以调试,所以在【实现逻辑冗长】时,我们应该避免使用内联处理函数,而是直接采用常规方式,就像这样:
const Item = ({ item, onRemoveItem }) => { const handleRemoveItem = () => { onRemoveItem(item); }; return ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> <span> <button onClick={handleRemoveItem}>Dismiss</button> </span> </div> ); }; 复制代码
异步数据 & 条件渲染
在真实应用中,我们一般先要渲染出一个组件,然后再请求第三方 API 得到数据并将其显示在组件上。
我们使用一个空数组作为初始的 state,然后通过一个简单的 promise 函数加上一点延迟来【模拟异步获取数据的过程】:
const getAsyncStories = () => new Promise((resolve) => setTimeout(() => resolve({ data: { stories: initialStories } }), 2000) ); const App = () => { ... const [stories, setStories] = React.useState([]); React.useEffect(() => { getAsyncStories().then((res) => { setStories(res.data.stories); }); }, []); ... }; 复制代码
由于 useEffect 的依赖数组是【空数组】,因此副作用函数仅在组件首次渲染后执行,函数在解析完 promise 并修改 state 后,组件会再次渲染并显示异步加载的数据。
关于 promise 的用法这里写一个示例,查看文档阅读更多。
const makeServerRequest = new Promise((resolve, reject) => { // 做一些异步操作,假设从远端API调取数据 // 成功拿到数据调用resolve(),失败调用reject() if(responseFromServer) { resolve("We got the data"); } else { reject("Data not received"); } }); // resolve()执行时调用 makeServerRequest.then(result => { console.log(result); // "We got the data" }); // reject()执行时调用 makeServerRequest.catch(error => { console.log(error); // "Data not received" }); 复制代码
处理异步数据使我们处于有数据和无数据这两种【条件状态】,我们需要在数据加载时为用户显示一个加载指示器:
const [isLoading, setIsLoading] = React.useState(false); React.useEffect(() => { setIsLoading(true); getAsyncStories().then((res) => { setStories(res.data.stories); setIsLoading(false); }); }, []); 复制代码
通过三元运算符在 JSX 中内联条件渲染,当 isLoading
条件为 true,显示一个提示标签:
const App = () => { ... return ( <> ... <hr /> {isLoading ? ( <p>Loading...</p> ) : ( <List list={searchedStories} onRemoveItem={handleRemoveStory} /> )} </> ); }; 复制代码
真实应用中调用第三方 API 的数据很有可能会出错,我们引入另一个 state,然后使用 Promise.catch()
来处理错误状态:
const App = () => { const [searchTerm, setSearchTerm] = useSemiPersistentState( "search", "React" ); const [stories, setStories] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); const [isError, setIsError] = React.useState(false); React.useEffect(() => { setIsLoading(true); getAsyncStories() .then((res) => { setStories(res.data.stories); setIsLoading(false); }) .catch(() => setIsError(true)); }, []); ... }; 复制代码
然后使用逻辑与(&&) 运算符,条件渲染一些内容为用户提供反馈:
const App = () => { ... return ( <> ... <hr /> {isError && <p>Something went wrong ...</p>} {isLoading ? ( <p>Loading...</p> ) : ( <List list={searchedStories} onRemoveItem={handleRemoveStory} /> )} </> ); }; 复制代码
在 React 中,我们经常使用 条件 && JSX
的方式来避免返回 null 值,当左侧条件成立则渲染右侧内容,反之跳过右侧内容。