2.React组件化

课堂目标

  • 掌握组件化开发中多种实现技术
  • 了解组件化概念,能设计并实现⾃⼰需要的组件
  • 掌握使⽤跨层级通信-Context(新API在v>=16.3)
  • 组件复合 - Composition
  • ⾼阶组件 - HOC
  • Hooks(>=16.8)
  • 掌握第三⽅组件的使⽤

知识要点

  • 运⽤Context
  • 运⽤组件复合 - Composition
  • 运⽤⾼阶组件 - HOC
  • Hooks使⽤
  • 使⽤umi antD

资源

antd-pro安装参考:https://pro.ant.design/docs/getting-startedcn#%E5%AE%89%E8%A3%85

起步

组件化优点:

  • 增强代码重⽤性,提⾼开发效率
  • 简化调试步骤,提升整个项⽬的可维护性
  • 便于协同开发

快速开始

Create React App 中文文档

npx create-react-app my-app
cd my-app
npm start

组件跨层级通信 - Context

React中使⽤Context实现祖代组件向后代组件跨层级传值。Vue中的provide & inject来源于Context

在Context模式下有两个⻆⾊

  • Provider:外层提供数据的组件
  • Consumer :内层获取数据的组件

使⽤Context

创建Context => 获取Provider和Consumer => Provider提供值 =>Consumer消费值

范例:模拟redux存放全局状态,在组件间共享

//App.js
import React from 'react';
import Home from './pages/Home'
import User from './pages/User'
import {
  Provider
}
from './AppContext' //引⼊Context的Provider 
const store = {
  home: {
    imgs: [{
      "src": "//m.360buyimg.com/mobilecms/s700x280_jfs/t1/49973/2/8672/125419/5d679259Ecd46f8e7/0669f8801dff67e8.jpg!cr_1125x445_0_171!q70.jpg.dpg"
    }]
  },
  user: {
    isLogin: true,
    userName: "true"
  }
}
function App() {
  return ( 
  <div classname="app"> 
   <provider value="{store}"> 
    <home /> 
   </provider> 
  </div>
  );
}
export default App;

AppContext.js

import React, { Component } from 'react'
export const Context = React.createContext()
export const Provider = Context.Provider
export const Consumer = Context.Consumer

/pages/Home.js

import React, { Component } from 'react'
import { Consumer } from '../AppContext'
export default class Home extends Component {
  render() {
    return <Consumer>{(ctx) => <HomeCmp {...ctx} />}</Consumer>
  }
}
function HomeCmp(props) {
  const { home, user } = props
  const { isLogin, userName } = user
  return <div>{isLogin ? userName : '登录'}</div>
}

/pages/User.js

import React, { Component } from 'react'
import { Consumer } from '../AppContext'
import TabBar from '../components/TabBar'
export default class User extends Component {
  render() {
    return (
      <>
        <Consumer>{(ctx) => <UserCmp {...ctx} />}</Consumer>
        <TabBar />
      </>
    )
  }
}
function UserCmp(props) {
  const { home, user } = props
  const { isLogin, userName } = user
  return <div>{isLogin ? userName : '登录'}</div>
}

/components/TabBar

import React from 'react'
import { Consumer } from '../AppContext'
export default function TabBar() {
  return (
    <div>
      <Consumer>{(ctx) => <TabBarCmp {...ctx} />}</Consumer>
    </div>
  )
}
function TabBarCmp(props) {
  const { home, user } = props
  const { isLogin, userName } = user
  return <div>{isLogin ? userName : '登录'}</div>
}

在React的官⽅⽂档中,Context被归类为⾼级部分(Advanced),属于React的⾼级API,但官⽅并不建议在稳定版的App中使⽤Context。

不过,这并⾮意味着我们不需要关注Context。事实上,很多优秀的React组件都通过Context来完成⾃⼰的功能,⽐如react-redux的<Provider />,就是通过Context提供⼀个全局态的store,拖拽组件react-dnd,通过Context在组件中分发DOM的Drag和Drop事件,路由组件react-router通过Context管理路由状态等等。在React组件开发中,如果⽤好Context,可以让你的组件变得强⼤,⽽且灵活。

函数组件中可以通过useContext引⼊上下⽂,后⾯hooks部分介绍

Composition-组件复合

复合组件给与你⾜够的敏捷去定义⾃定义组件的外观和⾏为,这种⽅式更明确和安全。如果组件间有公⽤的⾮UI逻辑,将它们抽取为JS模块导⼊使⽤⽽不是继承它。

Composition-基本使⽤

/pages/Layout.js【不具名】

import React, { Component } from 'react'
export default class Layout extends Component {
  componentDidMount() {
    const { title = '商城' } = this.props
    document.title = title
  }
  render() {
    const { children, title = '商城' } = this.props
    return (
      <div style={{ background: 'yellow' }}>
        <p>{title}</p>
        {children.btns ? children.btns : children}
        <TabBar />
      </div>
    )
  }
}
function TabBar(props) {
  return <div>TabBar</div>
}

/pages/Home.js

import React, { Component } from 'react'
import { Consumer } from '../AppContext'
import Layout from './Layout'
export default class Home extends Component {
  render() {
    return <Consumer>{(ctx) => <HomeCmp {...ctx} />}</Consumer>
  }
}
function HomeCmp(props) {
  const { home, user } = props
  const { carsouel = [] } = home
  const { isLogin, userName } = user
  return (
    <Layout title="⾸⻚">
      <div>
        <div>{isLogin ? userName : '未登录'}</div>
        {carsouel.map((item, index) => {
          return <img key={'img' + index} src={item.img} />
        })}
      </div>
    </Layout>
  )
}

/pages/User.js【传个对象进去就是具名插槽】

import React, { Component } from 'react'
import { Consumer } from '../AppContext'
import Layout from './Layout'
export default class User extends Component {
  render() {
    return (
      <div>
        <p>⽤户中⼼</p>
        <Consumer>{(ctx) => <UserCmp {...ctx} />}</Consumer>
      </div>
    )
  }
}
function UserCmp(props) {
  const { home, user } = props
  const { carsouel = [] } = home
  const { isLogin, userName } = user
  return (
    <Layout title="⽤户中⼼">
      {{
        btns: <button>下载</button>,
      }}
      {/* <div>
 <div>⽤户名: {isLogin ? userName : '未登录'}
</div>
 </div> */}
    </Layout>
  )
}

实现⼀个简单的复合组件,如antD的Card

import React, { Component } from 'react'
function Card(props) {
  return <div className="card">{props.children}</div>
}
function Formbutton(props) {
  return (
    <div className="Formbutton">
      <button onClick={props.children.defaultBtns.searchClick}>
        默认查询
      </button>
      <button onClick={props.children.defaultBtns.resetClick}>
        默认重 置
      </button>
      {props.children.btns.map((item, index) => {
        return (
          <button key={'btn' + index} onClick={item.onClick}>
            {item.title}
          </button>
        )
      })}
    </div>
  )
}
export default class CompositionPage extends Component {
  render() {
    return (
      <div>
        <Card>
          <p>我是内容</p>
        </Card>
        CompositionPage
        <Card>
          <p>我是内容2</p>
        </Card>
        <Formbutton>
          {{
            /* btns: (
              <>
              <button onClick={() =>console.log('enn')}>查询</button>
              <button onClick={() =>console.log('enn2')}>查询2</button>
              </>
              ) 
            */
            defaultBtns: {
              searchClick: () => console.log('默认查询'),
              resetClick: () => console.log('默认重置'),
            },
            btns: [
              {
                title: '查询',
                onClick: () => console.log('查询'),
              },
              {
                title: '重置',
                onClick: () => console.log('重置'),
              },
            ],
          }}
        </Formbutton>
      </div>
    )
  }
}

HOC-⾼阶组件

为了提⾼组件复⽤率,可测试性,就要保证组件功能单⼀性;但是若要满⾜复杂需求就要扩展功能单⼀的组件,在React⾥就有了HOC(Higher-Order Components)的概念,定义:⾼阶组件是⼀个⼯⼚函数,它接收⼀个组件并返回另⼀个组件

HOC-基本使⽤

HocPage.js

import React from 'react'
function Child(props) {
  return <div>Child</div>
}
const foo = (Cmp) => (props) => {
  return <Cmp {...props} />
}
/*const foo = (Cmp) => {
  return (props) => {
  return <Cmp {...props} />
  }
 }*/
export default function HocPage(props) {
  const Foo = foo(Child)
  return (
    <div>
      HocPage
      <Foo />
    </div>
  )
}

/pages/User.js【运⽤hoc改写前⾯的Context例⼦】

import React from 'react'
import { Consumer } from '../AppContext'
import Layout from './Layout'
const handleConsumer = (Cmp) => (props) => {
  return <Consumer>{(ctx) => <Cmp {...props}></Cmp>}</Consumer>
}
export default function User(props) {
  const HandleConsumer = handleConsumer(UserCmp)
  return (
    <Layout title="⽤户中⼼">
      <HandleConsumer />
    </Layout>
  )
}
function UserCmp(props) {
  console.log('user', props)
  return <div>User</div>
}

HOC-链式调⽤

import React from 'react'
function Child(props) {
  return <div>Child</div>
}
const foo = (Cmp) => (props) => {
  return (
    <div style={{ background: 'red' }}>
      <Cmp {...props} />
    </div>
  )
}
const foo2 = (Cmp) => (props) => {
  return (
    <div style={{ border: 'solid 1px green' }}>
      <Cmp {...props} />
    </div>
  )
}
export default function HocPage() {
  const Foo = foo2(foo(Child))
  return (
    <div>
      HocPage
      <Foo />
    </div>
  )
}

HOC-装饰器写法

⾼阶组件本身是对装饰器模式的应⽤,⾃然可以利⽤ES7中出现的装饰器语法来更优雅的书写代码。 CRA项⽬中默认不⽀持js代码使⽤装饰器语法,可修改后缀名为tsx则可以直接⽀持

// 装饰器只能⽤在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>
  )
}

Hooks

Hook是React16.8⼀个新增项,它可以让你在不编写 class 的情况下使⽤ state 以及其他的 React 特性。

Hooks的特点

  • 使你在⽆需修改组件结构的情况下复⽤状态逻辑
  • 可将组件中相互关联的部分拆分成更⼩的函数,复杂组件将变得
  • 更容易理解
  • 更简洁、更易理解的代码

State Hook-状态钩⼦

创建HookPage.js

import React, { useState, useEffect } from 'react'
export default function HookPage() {
  const [date, setDate] = useState(new Date())
  useEffect(() => {
    const timerId = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => clearInterval(timerId)
  })
  return (
    <div>
      <h1>Home ⻚⾯</h1>
      <div>{date.toLocaleTimeString()}</div>
    </div>
  )
}

更新函数类似setState,但它不会整合新旧状态

声明多个状态变量

import React, { useState, useEffect } from 'react'
export default function HookPage() {
  const [date, setDate] = useState(new Date())
  const [fruits, setFruits] = useState(['apple', 'banana', 'berry'])
  useEffect(() => {
    const timerId = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => clearInterval(timerId)
  })
  const del = (delIndex) => {
    const tem = [...fruits]
    tem.splice(delIndex, 1)
    setFruits(tem)
  }
  return (
    <div>
      <h1>Home ⻚⾯</h1>
      <div>{date.toLocaleTimeString()}</div>
      <FruitList fruits={fruits} onSetFruits={del} />
    </div>
  )
}
function FruitList({ fruits, onSetFruits }) {
  return (
    <>
      <h2>点击下⾯⽔果删除当前</h2>
      <ul>
        {fruits.map((item, index) => {
          return (
            <li
              key={'fruit' + index}
              onClick={() => onSetFruits(index)}
            >
              {item}
            </li>
          )
        })}
      </ul>
    </>
  )
}

⽤户输⼊处理

import React, { useState, useEffect } from 'react'
export default function HookPage() {
  const [date, setDate] = useState(new Date())
  const [fruits, setFruits] = useState(['apple', 'banana', 'berry'])
  useEffect(() => {
    const timerId = setInterval(() => {
      setDate(new Date())
    }, 1000)
    return () => clearInterval(timerId)
  }) //副作⽤ , [date]);
  const del = (delIndex) => {
    const tem = [...fruits]
    tem.splice(delIndex, 1)
    setFruits(tem)
  }
  return (
    <div>
      <h1>Home ⻚⾯</h1>
      <div>{date.toLocaleTimeString()}</div>
      <FruitAdd onAdd={(item) => setFruits([...fruits, item])} />
      <FruitList fruits={fruits} onSetFruits={del} />
    </div>
  )
}
function FruitList({ fruits, onSetFruits }) {
  return (
    <>
      <h2>点击下⾯⽔果删除当前</h2>
      <ul>
        {fruits.map((item, index) => {
          return (
            <li
              key={'fruit' + index}
              onClick={() => onSetFruits(index)}
            >
              {item}
            </li>
          )
        })}
      </ul>
    </>
  )
}
function FruitAdd(props) {
  const [name, setName] = useState('')
  return (
    <div>
      <h2>增加⽔果</h2>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button onClick={() => props.onAdd(name)}>add</button>
    </div>
  )
}

Effect Hook-副作⽤钩⼦

useEffect:给函数组件增加了执⾏副作⽤操作的能⼒。

副作⽤(Side Effffect):是指⼀个 function 做了和本身运算返回值⽆关的事,⽐如:修改了全局变量、修改了传⼊的参数、甚⾄是console.log(),所以 ajax 操作,修改 dom 都是算作副作⽤。

异步数据获取,更新HooksTest.js

import { useEffect } from 'react'
useEffect(() => {
  setTimeout(() => {
    setFruits(['⾹蕉', '⻄⽠'])
  }, 1000)
})

测试会发现副作⽤操作会被频繁调⽤

设置依赖

// 设置空数组意为没有依赖,则副作⽤操作仅执⾏⼀次
useEffect(()=>{...}, [])

如果副作⽤操作对某状态有依赖,务必添加依赖选项

useEffect(() => {
  document.title = fruit
}, [fruit])

清除⼯作:有⼀些副作⽤是需要清除的,清除⼯作⾮常重要的,可以防⽌引起内存泄露

useEffect(() => {
  const timer = setInterval(() => {
    console.log('msg')
  }, 1000)

  return function () {
    clearInterval(timer)
  }
}, [])

 组件卸载后会执⾏返回的清理函数

Hooks之useReducer

useReducer:useReducer是useState的可选项,常⽤于组件有复杂状态逻辑时,类似于redux中reducer概念。

商品列表状态维护

import React, { useReducer, useEffect } from 'react'
import { FruitList, FruitAdd } from './Fruit'
function fruitReducer(state, action) {
  switch (action.type) {
    case 'init':
    case 'replace':
      return action.payload
    case 'add':
      return [...state, action.payload]
    default:
      return state
  }
}
export default function HookReducer() {
  const [fruits, dispatch] = useReducer(fruitReducer, [])
  useEffect(() => {
    setTimeout(() => {
      dispatch({ type: 'init', payload: ['apple', 'banana'] })
    }, 1000)
  }, [])
  return (
    <div>
      <h1>User ⻚⾯</h1>
      <FruitAdd
        onAdd={(item) => dispatch({ type: 'add', payload: item })}
      />
      <FruitList
        fruits={fruits}
        onSetFruits={(cur) =>
          dispatch({ type: 'replace', payload: cur })
        }
      />
    </div>
  )
}

Fruit.js

import React, { useState } from 'react'
export function FruitList({ fruits, onSetFruits }) {
  const delCur = (delIndex) => {
    const tem = [...fruits]
    tem.splice(delIndex, 1)
    onSetFruits(tem)
  }
  return (
    <>
      <h2>点击下⾯⽔果删除当前</h2>
      <ul>
        {fruits.map((item, index) => {
          return (
            <li key={'fruit' + index} onClick={() => delCur(index)}>
              {item}
            </li>
          )
        })}
      </ul>
    </>
  )
}
export function FruitAdd(props) {
  const [name, setName] = useState('')
  return (
    <div>
      <h2>增加⽔果</h2>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <button onClick={() => props.onAdd(name)}>add</button>
    </div>
  )
}

Hooks之useContext

useContext⽤于在快速在函数组件中导⼊上下⽂

import React, { useContext } from 'react'
const Context = React.createContext()
const Provider = Context.Provider
export default function HookContext() {
  const store = {
    userName: 'xiaoming',
  }
  return (
    <div>
      <h1>HookContext ⻚⾯</h1>
      <Provider value={store}>
        <Child />
      </Provider>
    </div>
  )
}
function Child(props) {
  const { userName } = useContext(Context)
  return (
    <div>
      Child
      <div>userName: {userName}</div>
    </div>
  )
}

Hook相关拓展

第三⽅库

// antd-pro安装:
yarn create umi
// 选择 ant-design-pro
npm install
npm start

React组件化大纲

  • 课堂⽬标
  • 知识要点
  • 资源
  • 起步
  • 快速开始
  • 组件跨层级通信 - Context
    • 使⽤Context
  • 组件复合-Composition
    • 基本使⽤
  • ⾼阶组件-HOC
    • 基本使⽤
    • 链式调⽤
    • 装饰器写法
  • Hooks
    • 状态钩⼦ State Hook
    • 副作⽤钩⼦ Effect Hook
    • useReducer
    • useContext
    • Hook相关拓展
  • 第三⽅库
  • 下节课内容:第三⽅组件使⽤

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值