wallleap / myblogs

blog, Issue
https://myblog.wallleap.cn
4 stars 1 forks source link

再学 React 之函数组件 #107

Open wallleap opened 8 months ago

wallleap commented 8 months ago

title: 再学 React 之函数组件 date: 2024-04-02 16:54 updated: 2024-04-02 16:54 cover: //cdn.wallleap.cn/img/pic/cover/202302ihq49n.jpg category: 技术杂谈 tags:

之前学 React 的时候,版本还是 16,虽然可以用函数组件,但是用到 state 时只能用 Class 组件。Class 组件比函数组件代码量更多,而且需要写一些冗余的代码。React 16.8 新增了 Hooks API,而且现在官方文档也推荐写函数组件。

创建函数组件

直接定义一个函数,然后 return JSX(可以在 babel 在线网址上查看转换的 render/jsx 函数),这个函数就是一个函数组件

function App() {
  return <h2>这是一个函数组件</h2>
}
// 或者箭头函数
const App = () => <h2>这是一个函数组件</h2>

使用的时候可以调用它 App(),但推荐的方式是组件写成组件形式 <App />(JSX 中为了区分 HTML 元素和组件,规定组件以大写字母开头,并且需要闭合)

在一个组件内使用另一个组件的时候,直接导入并使用

return 后最好加上 () 包裹住 JSX 元素,由于组件只能返回一个元素,如果不想用 div 包裹可以使用 fragment(简写 <></>

import { createRoot } from "react-dom/client"

const App = () => {
  return (
    <>
      <h1>我是 App 组件</h1>
      <Child />
    </>
  )
}
const Child = () => <h2>我是 Child 组件</h2>

const rootElement = document.getElementById("root")
const root = createRoot(rootElement)

// root.render(App())
root.render(<App />)

props

可以直接在子组件上写属性,然后在接收的时候以参数形式获取

const App = () => {
  const msg = "Hello, "
  return (
    <>
      <h1>我是 App 组件</h1>
      <Child msg={msg} /> {/* JSX 里 JS 内容都写在 `{}` 中 */}
    </>
  )
}
const Child = (props) => <h2>{props.msg}我是 Child 组件</h2>

转用函数组件的两个问题

第一个问题可以通过 useState 解决,第二个问题可以用 useEffect 等 Hooks 模拟

useState 状态钩子

用法:

import { useState, StrictMode } from "react"
import { createRoot } from "react-dom/client"

const App = () => {
  const [count, setCount] = useState(0)
  const increment = () => setCount((count) => count + 1)
  return (
    <>
      <h2>{count}</h2>
      <button onClick={increment}>+</button> {/* 所有的属性名都是小驼峰写法,onClick、className */}
    </>
  )
}

const rootElement = document.getElementById("root")
const root = createRoot(rootElement)

root.render(
  <StrictMode>
    <App />
  </StrictMode> // 用于在开发环境下进行严格模式的检查
)

useEffect 副作用钩子模拟生命周期

useEffect 用法:

import { useEffect, useState, StrictMode } from "react"
import { createRoot } from "react-dom/client"

const App = () => {
  const [count, setCount] = useState(0)
  const [childVisible, setChildVisible] = useState(false)
  const increment = () => setCount((count) => count + 1)
  useEffect(() => {
    console.log("依赖项为 count", count) // 每次 count 变化都会执行
  }, [count])
  useEffect(() => {
    console.log("依赖项为所有", count) // 每次渲染都会执行
  })
  useEffect(() => {
    console.log("没有依赖项", count) // 只在首次渲染时执行
  }, [])
  return (
    <>
      <h2>{count}</h2>
      <button onClick={increment}>+</button>
      {childVisible ? (
        <button onClick={() => setChildVisible(false)}>hide</button>
      ) : (
        <button onClick={() => setChildVisible(true)}>show</button>
      )}
      {childVisible && <Child />}
    </>
  )
}

const Child = () => {
  const [m, setM] = useState(0)
  useEffect(() => {
    const timerId = setInterval(() => {
      setM((m) => m + 1)
    }, 1000)
    console.log("子组件依赖项 m", m) // m 变化执行
    return () => {
      console.log("return 的函数中") // 组件卸载执行
      clearInterval(timerId)
    };
  }, [m])
  return <div>我是子组件 {m}</div>
}

const rootElement = document.getElementById("root")
const root = createRoot(rootElement)

root.render(
  <StrictMode>
    <App />
  </StrictMode> // 用于在开发环境下进行严格模式的检查
)

总结:

JSX 中技巧

条件渲染

直接使用 if-else 或其他判断语句/运算符

let content
if (isLoggedIn) {
  content = <AdminPanel />
} else {
  content = <LoginForm />
}
const [visible, setVisible] = useState(false)
return (
  <>
    {content}
    isLoggedIn ? <AdminPanel /> : <LoginForm />
    visible && <Child />
  </>
)

列表渲染

JSX 中没办法直接使用 for 进行循环,可以使用数组的 map 方法或者将元素 push 到一个数组中

const products = [
  { title: 'Cabbage', id: 1 },
  { title: 'Garlic', id: 2 },
  { title: 'Apple', id: 3 },
]
const listItems = products.map(product =>
  <li
    key={product.id} // 必须有一个唯一的 key
    style={{ color: product.id % 2 === 0 ? "green" : "blue" }} // JSX 中 style 里面应该是一个对象
  >
    {product.title}
  </li>
)

return <ul>{listItems}</ul>

JSX 注释形式

function MyComponent() {
  return (
    // 在 JSX 周围
    <div>
      {/* JSX 里面 */}
      <Hello // 标签里面
        message="Hello, World!" // 标签里面
      /> 
    </div>
  )
}