React組件化練習

 
資源

Context參考

HOC參考

Hooks參考

組件跨層級通信 - Context

範例:模擬redux存放全局狀態,在組件間共享
 
import React from 'react'
// 創建上下文
const Context = React.createContext()
// 獲取Provider和Consumer
const Provider = Context.Provider
const Consumer = Context.Consumer
// Child顯示計數器,並能修改它,多個Child之間需要共享數據
function Child(props) {
  return <div onClick={() => props.add()}>{props.counter}</div>
}
export default class ContextTest extends React.Component {
  // state是要傳遞的數據
  state = {
    counter: 0,
  }
  // add方法可以修改狀態
  add = () => {
    this.setState((nextState) => ({ counter: nextState.counter + 1 }))
  }
  // counter狀態變更
  render() {
    return (
      <Provider value={{ counter: this.state.counter, add: this.add }}>
        {/* Consumer中內嵌函數,其參數是傳遞的數據,返回要渲染的組件 */}
        {/* 把value展開傳遞給Child */}
        <Consumer>{(value) => <Child {...value} />}</Consumer>
        <Consumer>{(value) => <Child {...value} />}</Consumer>
      </Provider>
    )
  }
}

 

高階組件

範例:爲展示組件添加獲取數據能力
// Hoc.js
import React from 'react'
// Lesson保證功能單一,它不關心數據來源,只負責顯示
function Lesson(props) {
  return (
    <div>
      {props.stage} - {props.title}
    </div>
  )
}
// 模擬數據
const lessons = [
  { stage: 'React', title: '核心API' },
  { stage: 'React', title: '組件化1' },
  { stage: 'React', title: '組件化2' },
]
// 高階組件withContent負責包裝傳入組件Comp
// 包裝後組件能夠根據傳入索引獲取課程數據,真實案例中可以通過api查詢得到
const withContent = (Comp) => (props) => {
  const content = lessons[props.idx]
  // {...props}將屬性展開傳遞下去
  return <Comp {...content} />
}
// LessonWithContent是包裝後的組件
const LessonWithContent = withContent(Lesson)
export default function HocTest() {
  // HocTest渲染三個LessonWithContent組件
  return (
    <div>
      {[0, 0, 0].map((item, idx) => (
        <LessonWithContent idx={idx} key={idx} />
      ))}
    </div>
  )
}

 

範例:改造前面案例使上下文使用更優雅

鏈式調用

// withConsumer是高階組件工廠,它能根據配置返回一個高階組件
function withConsumer(Consumer) {
  return (Comp) => (props) => {
    return <Consumer>{(value) => <Comp {...value} {...props} />}</Consumer>
  }
}
// Child顯示計數器,並能修改它,多個Child之間需要共享數據
// 新的Child是通過withConsumer(Consumer)返回的高階組件包裝所得
const Child = withConsumer(Consumer)(function (props) {
  return (
    <div onClick={() => props.add()} title={props.name}>
      {props.counter}
    </div>
  )
})
export default class ContextTest extends React.Component {
  render() {
    return (
      <Provider value={{ counter: this.state.counter, add: this.add }}>
        {/* 改造過的Child可以自動從Consumer獲取值,直接用就好了 */}
        <Child name="foo" />
        <Child name="bar" />
      </Provider>
    )
  }
}
// 高階組件withLog負責包裝傳入組件Comp
// 包裝後組件在掛載時可以輸出日誌記錄
const withLog = (Comp) => {
  // 返回組件需要生命週期,因此聲明爲class組件
  return class extends React.Component {
    render() {
      return <Comp {...this.props} />
    }
    componentDidMount() {
      console.log('didMount', this.props)
    }
  }
}
// LessonWithContent是包裝後的組件
const LessonWithContent = withLog(withContent(Lesson))

 

裝飾器寫法

CRA項目中默認不支持js代碼使用裝飾器語法,可修改後綴名爲tsx則可以直接支持
注意修改App.js中引入部分,添加一個後綴名
要求cra版本高於2.1.0

組件複合 - Composition

複合組件給與你足夠的敏捷去定義自定義組件的外觀和行爲

組件複合

範例:Dialog組件負責展示,內容從外部傳入即可,components/Composition.js
// 裝飾器只能用在class上
// 執行順序從下往上
@withLog
@withContent
class Lesson2 extends React.Component {
  render() {
    return (
      <div>
        {this.props.stage} - {this.props.title}
      </div>
    )
  }
}
export default function HocTest() {
  // 這裏使用Lesson2
  return (
    <div>
      {[0, 0, 0].map((item, idx) => (
        <Lesson2 idx={idx} key={idx} />
      ))}
    </div>
  )
}
import React from 'react'
// Dialog定義組件外觀和行爲
function Dialog(props) {
  return <div style={{ border: '1px solid blue' }}>{props.children}</div>
}
export default function Composition() {
  return (
    <div>
      {/* 傳入顯示內容 */}
      <Dialog>
        <h1>組件複合</h1>
        <p>複合組件給與你足夠的敏捷去定義自定義組件的外觀和行爲</p>
      </Dialog>
    </div>
  )
}

 

範例:傳個對象進去,key表示具名插槽
import React from 'react'
// 獲取相應部分內容展示在指定位置
function Dialog(props) {
  return (
    <div style={{ border: '1px solid blue' }}>
      {props.children.default}
      <div>{props.children.footer}</div>
    </div>
  )
}
export default function Composition() {
  return (
    <div>
      {/* 傳入顯示內容 */}
      <Dialog>
        {{
          default: (
            <>
              <h1>組件複合</h1>
              <p>複合組件給與你足夠的敏捷去定義自定義組件的外觀和行爲</p>
            </>
          ),
          footer: <button onClick={() => alert('react確實好')}>確定</button>,
        }}
      </Dialog>
    </div>
  )
}

 

如果傳入的是函數,還可以實現作用域插槽的功能
如果props.childrenjsx,此時它是不能修改的
範例:實現RadioGroupRadio組件,可通過RadioGroup設置Radioname
function RadioGroup(props) {
  // 不可行,
  // React.Children.forEach(props.children, child => {
  // child.props.name = props.name;
  // });
  return (
    <div>
      {React.Children.map(props.children, (child) => {
        // 要修改child屬性必須先克隆它
        return React.cloneElement(child, { name: props.name })
      })}
    </div>
  )
}

 

Hooks

準備工作

升級reactreact-dom

狀態鉤子 State Hook

創建HooksTest.js
 
// Radio傳入value,name和children,注意區分
function Radio({ children, ...rest }) {
  return (
    <label>
      <input type="radio" {...rest} />
      {children}
    </label>
  )
}
export default function Composition() {
  return (
    <div>
      {/* 執行顯示消息的key */}
      <RadioGroup name="mvvm">
        <Radio value="vue">vue</Radio>
        <Radio value="react">react</Radio>
        <Radio value="ng">angular</Radio>
      </RadioGroup>
    </div>
  )
}

 

npm i react react-dom -S

import React, { useState } from "react";
export default function HooksTest() {
// useState(initialState),接收初始狀態,返回一個由狀態和其更新函數組成的數組
const [fruit, setFruit] = useState("");
return (
<div>
<p>{fruit === "" ? "請選擇喜愛的水果:" : `您的選擇是:${fruit}`}</p>
</div>
);
}

 

聲明多個狀態變量
// 聲明列表組件
function FruitList({ fruits, onSetFruit }) {
  return (
    <ul>
      {fruits.map((f) => (
        <li key={f} onClick={() => onSetFruit(f)}>
          {f}
        </li>
      ))}
    </ul>
  )
}
export default function HooksTest() {
  // 聲明數組狀態
  const [fruits, setFruits] = useState(['香蕉', '西瓜'])
  return (
    <div>
      {/*添加列表組件*/}
      <FruitList fruits={fruits} onSetFruit={setFruit} />
    </div>
  )
}

 

用戶輸入處理

 

// 聲明輸入組件
function FruitAdd(props) {
  // 輸入內容狀態及設置內容狀態的方法
  const [pname, setPname] = useState('') // 鍵盤事件處理
  const onAddFruit = (e) => {
    if (e.key === 'Enter') {
      props.onAddFruit(pname)
      setPname('')
    }
  }
  return (
    <div>
      {' '}
      <input
        type="text"
        value={pname}
        onChange={(e) => setPname(e.target.value)}
        onKeyDown={onAddFruit}
      />{' '}
    </div>
  )
}
export default function HooksTest() {
  // ...
  return (
    <div>
      {' '}
      {/*添加水果組件*/}
      <FruitAdd onAddFruit={(pname) => setFruits([...fruits, pname])} />{' '}
    </div>
  )
}
useEffect 給函數組件增加了執行副作用操作的能力。
副作用(Side Effffect)是指一個 function 做了和本身運算返回值無關的事,比如:修改了全局變量、修改了傳入的
參數、甚至是 console.log(),所以 ajax 操作,修改 dom 都是算作副作用。
異步數據獲取,更新HooksTest.js
import { useEffect } from 'react'
useEffect(() => {
  setTimeout(() => {
    setFruits(['香蕉', '西瓜'])
  }, 1000)
}, []) // 設置空數組意爲沒有依賴,則副作用操作僅執行一次
如果副作用操作對某狀態有依賴,務必添加依賴選項
 
useEffect(() => {
  const timer = setInterval(() => {
    console.log('msg')
  }, 1000)
  return function () {
    clearInterval(timer)
  }
}, [])
清除工作:有一些副作用是需要清除的,清除工作非常重要的,可以防止引起內存泄露
 

useReducer

useReduceruseState的可選項,常用於組件有複雜狀態邏輯時,類似於reduxreducer概念。
商品列表狀態維護
useEffect(() => {
const timer = setInterval(() => {
console.log('msg');
}, 1000);
return function(){
clearInterval(timer);
}
}, []);
import { useReducer } from "react";
// 添加fruit狀態維護fruitReducer
function fruitReducer(state, action) {
switch (action.type) {
case "init":
return action.payload;
case "add":
return [...state, action.payload];
default:
return state;
}
}
export default function HooksTest() {
// 組件內的狀態不需要了
// const [fruits, setFruits] = useState([]);
// useReducer(reducer,initState)
const [fruits, dispatch] = useReducer(fruitReducer, []);
useEffect(() => {
setTimeout(() => {
// setFruits(["香蕉", "西瓜"]);
// 變更狀態,派發動作即可
dispatch({ type: "init", payload: ["香蕉", "西瓜"] });
}, 1000);
}, []);
return (
<div>
{/*此處修改爲派發動作*/}
<FruitAdd onAddFruit={pname => dispatch({type: 'add', payload: pname})} />
</div>
);
}

 

useContext

useContext用於在快速在函數組件中導入上下文。
Hooks相關拓展
  • 1. Hooks規則
  • 2. 自定義Hooks
  • 3. 一堆nb實現

推薦練習

1.嘗試利用hooks編寫一個完整的購物應用
2.基於useReducer的方式能否處理異步action
 
import React, { useContext } from "react";
// 創建上下文
const Context = React.createContext();
export default function HooksTest() {
// ...
return (
{/* 提供上下文的值 */}
<Context.Provider value={{fruits,dispatch}}>
<div>
{/* 這裏不再需要給FruitAdd傳遞狀態mutation函數,實現瞭解耦 */}
<FruitAdd />
</div>
</Context.Provider>
);
}
function FruitAdd(props) {
// 使用useContext獲取上下文
const {dispatch} = useContext(Context)
const onAddFruit = e => {
if (e.key === "Enter") {
// 直接派發動作修改狀態
dispatch({ type: "add", payload: pname })
setPname("");
}
};
// ...
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章