網站首頁 編程語言 正文
從問題出發
我被問過這樣一個問題:
想要實現一個 useTitle
方法,具體使用示例如下:
function Header() { const [Title, changeTitle] = useTitle(); return ( <div onClick={() => changeTitle('new title')}> <Title /> </div> ) }
但在編寫 useTitle
代碼的時候卻出了問題:
function TitleComponent({title}) { return <div>{title}</div> } function useTitle() { const [title, changeTitle] = useState('default title'); useEffect(() => { changeTitle(title) }, [title]) const Element = React.createElement(TitleComponent, {title}); return [Element.type, changeTitle]; }
這段代碼直接報錯,連渲染都渲染不出來,如果是你,該如何修改這段代碼呢?
元素與組件
其實這就是一個很典型的元素與組件如何區分和使用的問題。
元素
我們先看 React 官方文檔中對 React 元素的介紹:
Babel 會把 JSX 轉譯成一個名為 React.createElement()
函數調用。以下兩種示例代碼完全等效:
const element = <h1 className="greeting">Hello, world!</h1>; const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
React.createElement()
會預先執行一些檢查,以幫助你編寫無錯代碼,但實際上它創建了一個這樣的對象:
// 注意:這是簡化過的結構 const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world!' } };
這些對象被稱為 “React 元素”。它們描述了你希望在屏幕上看到的內容。
你看,React 元素其實就是指我們日常編寫的 JSX 代碼,它會被 Babel 轉義為一個函數調用,最終得到的結果是一個描述 DOM 結構的對象,它的數據結構本質是一個 JS 對象。
在 JSX 中,我們是可以嵌入表達式的,就比如:
const name = 'Josh Perez'; const element = <h1>Hello, {name}</h1>;
所以如果我們要使用一個 React 元素,那我們應該使用嵌入表達式這種方式:
const name = <span>Josh Perez</span>; const element = <h1>Hello, {name}</h1>;
組件
那組件呢?組件有兩種,函數組件和 class 組件:
// 函數組件 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } // class 組件 class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
那如何使用組件呢?
const element = <Welcome name="Sara" />;
對于組件,我們要使用類似于 HTML 標簽的方式進行調用,Babel 會將其轉譯為一個函數調用
const element = React.createElement(Welcome, { name: "Sara" });
所以你看,組件的數據結構本質是一個函數或者類,當你使用元素標簽的方式進行調用時,函數或者類會被執行,最終返回一個 React 元素。
問題如何解決
盡管這些內容都來自于 React 官方文檔,但如果你能清晰的了解到 React 元素和組件的差別,你已經可以解決開頭的問題了。至少有兩種方式可以解決,一種是返回 React 元素,一種是返回 React 組件
第一種我們返回 React 元素:
const root = ReactDOM.createRoot(document.getElementById('root')); function Header() { const [Title, changeTitle] = useTitle(); // 這里因為返回的是 React 元素,所以我們使用 {} 的方式嵌入表達式 return ( <div onClick={() => changeTitle('new title')}> {Title} </div> ) } function TitleComponent({title}) { return <div>{title}</div> } function useTitle() { const [title, changeTitle] = useState('default title'); useEffect(() => { changeTitle(title) }, [title]) // createElement 返回的是 React 元素 const Element = React.createElement(TitleComponent, {title}); return [Element, changeTitle]; } root.render(<Header />);
第二種我們返回 React 組件:
const root = ReactDOM.createRoot(document.getElementById('root')); function Header() { const [Title, changeTitle] = useTitle(); // 因為返回的是 React 組件,所以我們使用元素標簽的方式調用 return ( <div onClick={() => changeTitle('new title')}> <Title /> </div> ) } function TitleComponent({title}) { return <div>{title}</div> } function useTitle() { const [title, changeTitle] = useState('default title'); useEffect(() => { changeTitle(title) }, [title]) // 這里我們構建了一個函數組件 const returnComponent = () => { return <TitleComponent title={title} /> } // 這里我們直接將組件返回出去 return [returnComponent, changeTitle]; } root.render(<Header />);
自定義內容
有的時候我們需要給組件傳入一個自定義內容。
舉個例子,我們實現了一個 Modal 組件,有確定按鈕,有取消按鈕,但 Modal 展示的內容為了更加靈活,我們提供了一個 props 屬性,用戶可以自定義一個組件傳入其中,用戶提供什么,Modal 就展示什么,Modal 相當于一個容器,那么,我們該怎么實現這個功能呢?
第一種實現方式
以下是第一種實現方式:
function Modal({content}) { return ( <div> {content} <button>確定</button> <button>取消</button> </div> ) } function CustomContent({text}) { return <div>{text}</div> } <Modal content={<CustomContent text="content" />} />
根據前面的知識,我們可以知道,content
屬性這里傳入的其實是一個 React 元素,所以 Modal 組件的內部是用 {}
進行渲染。
第二種實現方式
但第一種方式,并不總能解決需求。有的時候,我們可能會用到組件內部的值。
就比如一個倒計時組件 Timer
,依然提供了一個屬性 content
,用于自定義時間的展示樣式,時間由 Timer
組件內部處理,展示樣式則完全由用戶自定義,在這種時候,我們就可以選擇傳入一個組件:
function Timer({content: Content}) { const [time, changeTime] = useState('0'); useEffect(() => { setTimeout(() => { changeTime((new Date).toLocaleTimeString()) }, 1000) }, [time]) return ( <div> <Content time={time} /> </div> ) } function CustomContent({time}) { return <div style={{border: '1px solid #ccc'}}>{time}</div> } <Timer content={CustomContent} />
在這個示例中,我們可以看到 content
屬性傳入的是一個 React 組件 CustomContent,而 CustomContent 組件會被傳入 time 屬性,我們正是基于這個約定進行的 CustomContent 組件的開發。
而 Timer 組件內部,因為傳入的是組件,所以使用的是 <Content time={time}/>
進行的渲染。
第三種實現方式
在面對第二種實現方式的需求時,除了上面這種實現方式,還有一種稱為 render props
的技巧,比第二種方式更常見一些,我們依然以 Timer 組件為例:
function Timer({renderContent}) { const [time, changeTime] = useState('0'); useEffect(() => { setTimeout(() => { changeTime((new Date).toLocaleTimeString()) }, 1000) }, [time]) // 這里直接調用傳入的 renderContent 函數 return ( <div> {renderContent(time)} </div> ) } function CustomContent({time}) { return <div style={{border: '1px solid #ccc'}}>{time}</div> } root.render(<Timer renderContent={(time) => { return <CustomContent time={time} /> }} />);
鑒于我們傳入的是一個函數,我們把 content
屬性名改為了 renderContent
,其實叫什么都可以。
renderContent
傳入了一個函數,該函數接收 time
作為參數,返回一個 React 元素,而在 Timer
內部,我們直接執行了 renderContent 函數,并傳入內部處理好的 time 參數,由此實現了用戶使用組件內部值自定義渲染內容。
多說一句,除了放到屬性里,我們也可以放到 children 里,是一樣的:
function Timer({children}) { // ... return ( <div> {children(time)} </div> ) } <Timer> {(time) => { return <CustomContent time={time} /> }} </Timer>
我們可以視情況選擇合適的傳入方法。
React 系列
React 之 createElement 源碼解讀
原文鏈接:https://juejin.cn/post/7161320926728945701
相關推薦
- 2022-03-16 Linux系統中日志詳細介紹_Linux
- 2022-04-24 C/C++字節序的深入理解_C 語言
- 2022-07-29 Docker容器數據卷技術介紹_docker
- 2022-12-03 Nginx部署SSL證書的過程_nginx
- 2022-03-17 解決.Net?Core項目發布在IIS上訪問404的問題_實用技巧
- 2022-07-13 JMeter主要元件_線程組的使用方法
- 2022-04-23 uniapp用Promise封裝get和post請求
- 2022-01-12 錯誤:UnmappedTerms cannot be cast to org.elasticsear
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支