import React from 'react'; import propsTypes from 'props-types'; const UserCard = ({ user }) => { return ( <ul> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> </ul> ) } UserCard.propsTypes = { user: propsTypes.object } UserCard.defaultTypes = { user: {} }这个组件需要一个 user props 并且它是组件的唯一数据源,但是这个 props 不是必须的(没有设置 isRequired),所以设置了一个默认值为 {},以避免 Can not access property‘ name’ of... errors 等错误。那么,如果没有为 <UserCard> 组件在等待渲染时提供一个回退值 (fallback),并且 UserCard 没有任何数据也没有任何逻辑运行,那么就没有理由呈现这个组件。
targetCurrency - 我们要转换成的货币。
import React, { useState } from "react"; import PropTypes from "prop-types"; // 堆代码 duidaima.com // 子组件 export const UserCard = ({ user }) => { const keys = Object.keys(user) return ( keys.length ? <ul> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> </ul> : "No Data" ); };我们看到一个组件带有一些逻辑,却徒劳地执行,只是为了显示一个 spinner 或一条信息。针对这种情况,请记住,在父组件内部完成此操作总是比在组件本身内部完成更为干净。按着这个原则,子组件和父组件应该像这样:
import React, { useState } from "react"; import PropTypes from "prop-types"; // 堆代码 duidaima.com // 子组件 export const UserCard = ({ user }) => { return ( <ul> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> </ul> ); }; UserCard.propTypes = { user: PropTypes.object.isRequired }; // 父组件 export const UserContainer = () => { const [user, setUser] = useState(null); // do some apiCall here return ( <div> {user && <UserCard user={user} />} </div> ); };通过这种方式,我们将 user 初始化为 null,然后简单的运行一个 falsy 检测,如果 user 不存在,!user 将返回 true。如果设置为 {} 则不然,我们必须通过 Object.keys() 检查对象 key 的长度,通过不创建新的对象引用,我们节省了一些内存空间,并且只有在获得了所需的数据之后,我们才渲染子组件 <UserCard/>。
export const UserContainer = () => { const [user, setUser] = useState(null); // do some apiCall here return ( <div> {user ? <UserCard user={user} /> : 'No data available'} </div> ); };子组件 <UserCard/> 只负责显示用户数据,父组件 <UserContainer/> 是用来获取数据并决定呈现什么的。这就是为什么父组件是显示回退值(fallback)最佳位置的原因。
const NestedComponent = () => { // ... return ( <> {!isLoading ? ( <> <h2>Some heading</h2> <p>Some description</p> </> ) : <Spinner />} </> ) }该怎么做才是更合理的呢?
const NestedComponent = () => { // ... if (isLoading) return <Spinner /> return ( <> <h2>Some heading</h2> <p>Some description</p> </> ) }我们处理 render 逻辑时,在处理是否有可用的数据,页面是否正在加载,我们都可以选择提前 return。这样我们就可以避免嵌套,不会把 HTML 和 JavaScript 混合在一起,而且代码对于不同技术水平或没有技术背景的人来说也是可读的。
const CustomInput = ({ onChange }) => { return ( <Input onChange={e => { const newValue = getParsedValue(e.target.value); onChange(newValue); }} /> ) }我们正在处理一些外部对 input 的一些输入,使用自定义处理程序解析该输入 e.target.value,然后将解析后的值传给 <CustomInput/> 组件接收的 onChange prop。虽然这个示例可以正常工作,但是会让代码变得很难理解。
const CustomInput = ({ onChange }) => { const handleChange = (e) => { const newValue = getParsedValue(e.target.value); onChange(newValue); }; return ( <Input onChange={handleChange} /> ) }当在 render 中返回 JSX 时,不要使用内联的 JavaScript 逻辑。
// eslint-disable-next-line react-hooks/exhaustive-deps import React, { useState, useEffect } from 'react' import { fetchUserAction } from '../api/actions.js' const UserContainer = () => { const [user, setUser] = useState(null); const handleUserFetch = async () => { const result = await fetchUserAction(); setUser(result); }; useEffect(() => { handleUserFetch(); // 忽略警告 // eslint-disable-next-line react-hooks/exhaustive-deps }, []); if (!user) return <p>No data available.</p> return <UserCard data={user} /> };最初,很多人认为这个警告毫无意义,从而选择进行忽略,而不去试图探索它是如何产生的。其实,有些人没有意识到,handleUserFetch() 方法在组件每次渲染的时候都会重新创建(组件有多少次更新就会创建多少次)。useCallback 的作用在于利用 memoize 减少无效的 re-render,来达到性能优化的作用。
import React, { useState, useEffect, useCalllback } from 'react' import { fetchUserAction } from '../api/actions.js' const UserContainer = () => { const [user, setUser] = useState(null); // 堆代码 duidaima.com // 使用 useCallback 包裹 const handleUserFetch = useCalllback(async () => { const result = await fetchUserAction(); setUser(result); }, []); useEffect(() => { handleUserFetch(); }, [handleUserFetch]); /* 将 handleUserFetch 作为依赖项传入 */ if (!user) return <p>No data available.</p> return <UserCard data={user} /> };我们将 handleUserFetch 作为 useEffect 的依赖项,并将它包裹在 useCallback 中。如果此方法使用外部参数,例如 userId (在实际开发中,可能希望获取特定的用户) ,则此参数可以作为 useCallback 的依赖项传入。只有 userId 发生变化时,依赖它的 handleUserFetch 才重写改变。
const UserCard = ({ user }) => { const getUserRole = () => { const { roles } = user; if (roles.includes('admin')) return 'Admin'; if (roles.includes('maintainer')) return 'Maintainer'; return 'Developer'; } return ( <ul> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> <li>{getUserRole()}</li> </ul> ); }这个方法和前一个例子中的方法一样,在组件每次渲染时都会重新创建,(但是没必要使用 useCallback 进行包裹,因为它没有被作为一个依赖项传入)。
const getUserRole = (roles) => { if (roles.includes('admin')) return 'Admin'; if (roles.includes('maintainer')) return 'Maintainer'; return 'Developer'; } const UserCard = ({ user }) => { return ( <ul> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> <li>{getUserRole(user.roles)}</li> </ul> ); }通过这种方式,可以在一个单独的文件中定义函数,并在需要时导入,进而可能会达到复用的目的。早期将逻辑从组件中抽象出来,可以让我们拥有更简洁的组件和易于重用的实用函数。
JavaScript 语言:负责网页的逻辑和交互,又称逻辑层或交互层。
<div style="width: 100%; height: 20px;">但是组件化(Vue、React)流行以后,打破了这个原则,它要求把 HTML、CSS、JavaScript 写在一起。
const style = { fontSize: "14px" } const UserCard = ({ user }) => { return ( <ul style={style}> <li>{user.name}</li> <li>{user.age}</li> <li>{user.email}</li> <li>{getUserRole(user.roles)}</li> </ul> ); }React 这么做有利于组件的隔离,每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,很方便复用。
const style1 = { fontSize: "14px" } const style2 = { fontSize: "12px", color: "red" } const style = {...} const UserCard = ({ user }) => { return ( <ul style={style}> <li style={style2}>{user.name}</li> <li style={color: "#333"}>{user.age}</li> <li style={color: "#333"}>{user.email}</li> <li style={color: "#333"}>{getUserRole(user.roles)}</li> </ul> ); }看到这里,有人可能会反驳:「你可以使用 props 有条件地对 CSS 内嵌样式进行样式化」,这是可行的,然而,你的组件不应该只有 10 个处理 CSS 的 props,而不做其他事情。如果非要在组件中编写 CSS,建议使用 style-components CSS-in-JS 库。
import React, { useState } from 'react'; const Form = () => { const [name, setName] = useState(''); const handleChange = e => { setName(e.target.value); } const handleSubmit = () => { // api call here } return ( <div> <input type="text" onChange={handleChange} value={name} /> <button type="button" onClick={handleSubmit}> Submit </button> </div> ) }这个示例所做的事是在 <input/> 上更新 name 值,并且在 <button/> 上绑定 click 事件,通过点击调用 handleSubmit 来提交数据。这个功能对于通过使用按钮进行提交的用户是可以的,但是对于使用 Enter 键提交表单的用户来说就不行了。
<form onsubmit="myFunction()"> Enter name: <input type="text"> <input type="submit"> </form>在 React 中 使用 onSubmit 是等效的:
import React, { useState } from 'react'; const Form = () => { const [name, setName] = useState(''); const handleChange = e => { setName(e.target.value); } const handleSubmit = e => { e.preventDefault(); // api call here } return ( <form onSubmit={handleSubmit}> <input type="text" onChange={handleChange} value={name} /> <button type="submit"> Submit </button> </form> ) }现在,这个表单提交适用于任何触发场景,而不仅仅是一个只支持通过按钮点击的表单。