课堂目标
- 掌握组件化开发中多种实现技术
- 了解组件化概念,能设计并实现⾃⼰需要的组件
- 掌握使⽤跨层级通信-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
起步
组件化优点:
- 增强代码重⽤性,提⾼开发效率
- 简化调试步骤,提升整个项⽬的可维护性
- 便于协同开发
快速开始
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相关拓展
- 第三⽅库
- 下节课内容:第三⽅组件使⽤