以结果为导向,写给刚学完前端三剑客和想要了解 React 框架的小伙伴,使得他们能快速上手(省略了历史以及一些不必要的介绍)。
props 处理
我们都知道 props 是一个 JS 对象,所以在传递时我们可以对它应用一些 JS 技巧,比如对象解构来访问其属性。
- 与数组解构类似,举个例子:
const user = { firstName: 'Man', lastName: 'cuoj', }; // 没用对象解构 const firstName = user.firstName; const lastName = user.lastName; // 用了 const { firstName, lastName } = user; 复制代码
- 实际应用,直接在函数签名里对 props 对象进行解构:
// 相当于在函数内部定义 const { onSearch, searchTerm } = props; const Search = ({ onSearch, searchTerm }) => { return ( <> <label htmlFor="search">Search: </label> <input id="search" type="text" onChange={onSearch} value={searchTerm} /> <p> Searching for <strong>{searchTerm}</strong>. </p> </> ); }; 复制代码
在 React 应用中,我们经常使用包含在 props 对象中的信息,而很少直接用到 props 对象。通过在函数签名中解构 props 对象,我们可以重构 List 组件,并从中提取出一个新的 Item 组件:
const List = ({ list }) => list.map((item) => <Item key={item.objectID} item={item} />); const Item = ({ item }) => ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> </div> ); 复制代码
可以看到 Item 组件中的 item 对象也没有被直接使用过,所以我们还可以采用【嵌套解构】的方式直接收集所有需要 item 对象的信息。
- 举个例子:
const user = { firstName: 'Man', pet: { name: 'dd', }, }; // 嵌套解构对象 const { firstName, pet: { name, }, } = user; 复制代码
- 实际应用:
const Item = ({ item: { url, title, author } }) => ( <div> <span> <a href={url}>{title}</a> </span> <span>{author}</span> </div> ); 复制代码
或者我们直接把 item 对象的每一个属性传给 Item 组件,而不再传递 item 对象:
const List = ({ list }) => list.map((item) => ( <Item key={item.objectID} title={item.title} url={item.url} author={item.author} /> )); const Item = ({ url, title, author }) => ( <div> <span> <a href={url}>{title}</a> </span> <span>{author}</span> </div> ); 复制代码
我们还可以通过 JS 的展开语法将对象的【所有键-值对】作为【属性-值对】传递给 JSX,从而使传递过程更加简洁:
const List = ({ list }) => list.map((item) => <Item key={item.objectID} {...item} />); 复制代码
继续通过 JS 的剩余参数把只用作 key 的 objectID
从 item 中分离出来,剩下的项作为属性-值对传递给 Item 组件:
const List = ({ list }) => list.map(({ objectID, ...item }) => <Item key={item.objectID} {...item} />); 复制代码
尽管这两个的语法都是三个点,但你不应该混淆它们。剩余运算符一般发生在解构的右侧,用于将一个对象和它的某些属性分开,展开运算符发生在左侧,用于展开对象的所有键值对。
虽然这样重构会使函数体变得很简洁,但是【可读性】会变得很差,毕竟展开运算符和剩余运算符并不是每个人都熟悉的,我们还是用回最初的版本:
const List = ({ list }) => list.map((item) => <Item key={item.objectID} item={item} />); const Item = ({ item }) => ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> </div> ); 复制代码
React 副作用
我们可以通过 localStorage
给 Search 组件添加一个新功能:当用户输入然后刷新浏览器标签页时,让浏览器记住最后一个搜索项。
const App = () => { ... const [searchTerm, setSearchTerm] = React.useState( localStorage.getItem("search") || "React" ); const handleSearch = (e) => { setSearchTerm(e.target.value); localStorage.setItem("search", e.target.value); }; ... }; 复制代码
不过处理函数应该只关心更新 state 的问题,但现在我们跨过了 React 的领域和浏览器的 API 互动产生了副作用,这是我们不能接受的。
所以这里我们引入 useEffect hook 在每次更新 state 即 searchTerm
发生改变时来触发副作用:
const App = () => { ... const [searchTerm, setSearchTerm] = React.useState( localStorage.getItem("search") || "React" ); React.useEffect(() => { localStorage.setItem("search", searchTerm); }, [searchTerm]); const handleSearch = (e) => { setSearchTerm(e.target.value); }; ... }; 复制代码
可以看到 useEffect hook 需要两个参数:
- 第一个参数是会产生副作用的函数
- 第二个参数其依赖的变量【数组】
如果任何一个依赖的变量发生改变,包含副作用的函数就会被调用。对于我们来说,当组件第一次渲染时,它会初始化调用一次,之后每次 searchTerm
的改变都会调用这个函数。
如果 useEffect 的依赖数组是个空数组,那么副作用函数只会在组件第一次渲染时被调用一次。