十三、组合vs继承
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
13.1 理解组件化
组件化是React的核心思想:
- 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类 组件:
- 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
vue 中有没有类组件和函数式组件?vue2中有
<template> </template> <script> export default { props:[], data () { return {} } } </script>
- vue中的函数式组件 - 无状态组件,所有的数据来源均来自父组件
<template functional> <div>{{props.msg}}</div> </template>
- vue 中的类组件 - 兼容ts时
<template> <div>hello vue</div> </template> class Home extends Vue {} // export default {}
- 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);
- 根据组件的不同职责,可以分成:展示型组件(Presentational Component - 只做数据的展示,一般不需要写更多的业务逻辑-数据请求不出现在展示型组件-顶多发出请求的指令-具体的请求交给容器型组件)和容器型组件(Container Component - 负责给展示性组件提供数据以及处理展示型组件需要的具体的业务逻辑) - 状态管理器-更容易理解;
这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
- 函数组件、无状态组件、展示型组件主要关注UI的展示;
- 类组件、有状态组件、容器型组件主要关注数据逻辑;
13.2 使用组合而非继承实现React组件化
有些组件无法提前知晓它们子组件的具体内容,建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中。
参照5.3章节
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
13.3 封装Modal弹窗
vue3中的 teleport
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './08_com/01App_modal'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/08_com/01App_modal.jsx
// src/08_com/01App_modal.jsx
import React, { Component } from 'react';
import './style.css'
class App extends Component {
state = {
visiable: false
}
render() {
return (
<div>
<button onClick={() => this.setState({ visiable: true })}>打开模态框</button>
{
this.state.visiable ? <div className="modal" onClick={(event) => {
console.log(event)
if (event.target.className === 'modal') { // vue中 modal的事件设置 修饰符 .self
this.setState({ visiable: false })
}
}}>
<div className="modal-box">
<div className="modal-header">
<span>模态框</span>
<span onClick={() => this.setState({ visiable: false })}>X</span>
</div>
<div className="modal-content">
内容
</div>
<div className="modal-footer">
<button onClick={() => this.setState({ visiable: false })}>取消</button>
<button>确认</button>
</div>
</div>
</div> : null
}
</div>
);
}
}
export default App
/* src/08_com/style.css */
* {padding: 0;margin: 0;}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-box {
width: 50%;
height: 400px;
margin: 100px auto;
background-color: #fff;
display: flex;
flex-direction: column;
}
.modal-box .modal-header {
height: 30px;
border-bottom: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 20px;
padding-right: 20px;
}
.modal-box .modal-content {
flex: 1;
padding-left: 20px;
padding-right: 20px;
}
.modal-box .modal-footer {
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: flex-end;
align-items: center;
}
.modal-box .modal-footer button {
margin-right: 20px;
height: 30px;
padding: 5px 20px;
}
审查元素发现 Modal 组件是渲染在原来的组件的位置的,如果想要让它渲染到不同的位置怎么办呢?
13.4 ReactDOM.createPortal()
普通的组件,子组件的元素将挂载到父组件的DOM节点中。
有时需要将元素渲染到DOM中的不同位置上去,这是就用到的portal的方法。
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './08_com/01App_modal' // 模态框
import App from './08_com/02App_portal' // 模态框 - portal
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/08_com/02App_portal.jsx
// src/08_com/02App_portal.jsx
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import './style.css'
function Modal (props) {
return ReactDOM.createPortal((
<div className="modal" onClick={(event) => {
console.log(event)
if (event.target.className === 'modal') { // vue中 modal的事件设置 修饰符 .self
props.hideModal()
}
}}>
<div className="modal-box">
<div className="modal-header">
<span>模态框</span>
<span onClick={() => props.hideModal()}>X</span>
</div>
<div className="modal-content">
内容
</div>
<div className="modal-footer">
<button onClick={() => props.hideModal()}>取消</button>
<button>确认</button>
</div>
</div>
</div>
), document.getElementsByTagName('body')[0])
}
class App extends Component {
state = {
visiable: false
}
hideModal = () => {
this.setState({
visiable: false
})
}
render() {
return (
<div>
<button onClick={() => this.setState({ visiable: true })}>打开模态框</button>
{
this.state.visiable ? <Modal hideModal = { this.hideModal }/> : null
}
</div>
);
}
}
export default App;
一个 portal 的典型用例是当父组件有
overflow: hidden或z-index样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框
十四、上下文Context
vue react
父子组件传值:vue props 父传子 事件 子传父;react props 父传子 事件 子传父(实际上还是父传子)
兄弟组件传值: vue eventBus react 状态提升 ---- 项目中不常见 - 状态管理器
祖先组件向后代组件传值: vue provide inject react Context
14.1 理解上下文、作用及其特点
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
14.2 使用React.createContext()
14.2.1 逐层传递数据
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './09_context/01App_next_value' // 逐层传递数据
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/09_context/01App_next_value.jsx
// src/09_context/01App_next_value/.jsx
import React, { Component } from 'react';
class Third extends Component {
render () {
return (
<>
<div>33333 --- { this.props.msg }</div>
</>
)
}
}
const Second = (props) => {
return (
<>
<div>2222 - { props.msg }</div>
<Third msg = { props.msg }/>
</>
)
}
class First extends Component {
render () {
return (
<>
<div>11111 - { this.props.msg }</div>
<Second msg = { this.props.msg }/>
</>
)
}
}
class App extends Component {
state = {
msg: '传家宝'
}
render() {
return (
<div>
<First msg = { this.state.msg }></First>
</div>
);
}
}
export default App;
14.2.2 使用Context传值
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './09_context/01App_next_value' // 逐层传递数据
import App from './09_context/02App_context' // Context 上下文传值
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/09_context/02App_context.jsx
// src/09_context/02App_context.jsx
import React, { Component } from 'react';
// 1.创建上下文对象
const MyContext = React.createContext()
const ColorContext = React.createContext()
// 3.如果后代组件是类组件,可以通过静态属性 contextType 来完成数据接收,通过 this.context 访问数据
// 还有一种等价写法,就是在定义组件之后,通过 组件.contextType = MyContext
class Third extends Component {
// static contextType = MyContext
render () {
return (
<>
<div>33333 - { this.context } </div>
</>
)
}
}
Third.contextType = MyContext
// 4.如果是函数式组件,推荐使用 上下文对象.Consumer 来取值,内部写的是函数,函数的参数即为需要的值
// 以上写法可以解决问题,但是在特定场景下 略显麻烦(祖先组件提供了多个值的时候)
// 现在主流推荐使用函数式组件,可以通过 hooks 解决问题
// const Second = (props) => {
// return (
// <>
// <div>
// 2222 -
// <MyContext.Consumer>
// {
// (val) => {
// return val
// }
// }
// </MyContext.Consumer>
// </div>
// <Third/>
// </>
// )
// }
const Second = (props) => {
const msg = React.useContext(MyContext) // hooks取值
const color = React.useContext(ColorContext)
return (
<>
<div>
2222 - { msg } - { color }
</div>
<Third/>
</>
)
}
class First extends Component {
render () {
return (
<>
<div>11111
<MyContext.Consumer>
{
(val) => {
return (
<div>
{val} -
<ColorContext.Consumer>
{
vl => {
return vl
}
}
</ColorContext.Consumer>
</div>
)
}
}
</MyContext.Consumer>
</div>
<Second/>
</>
)
}
}
class App extends Component {
state = {
msg: '传家宝'
}
render() {
return (
<div>
{/*
2.在祖先组件中,通过上下文对象提供的 Provider组件 ,配合value 属性传递数据
*/}
<MyContext.Provider value = { this.state.msg }>
<ColorContext.Provider value = "red" >
<First ></First>
</ColorContext.Provider>
</MyContext.Provider>
</div>
);
}
}
export default App;
14.2.3 传递多个值
类组件只能通过Context.Consumer解决,函数式组件还可以通过hooks解决
上述案例,还可以通过一个上下文对象传递多个值 - 自行测试
vue中祖先组件传递变量给后代组件,祖先组件状态发生改变,后代组件不会立即更新
<script> import { computed } from '@vue/reactivity'; import Child from './Child.vue' export default { components: { Child }, data () { return { count: 10 } }, provide () { return { count: computed(() => this.count) } } } </script> <template> <button @click="count++">加1</button> {{ count }} <Child /> </template> <style scoped> </style> <template> count: {{ count }} </template> <script> export default { inject: ['count'] } </script>
如果浏览器安装过 react的开发者工具,打开之后发现上述代码,都显示为
Context.Provider和Context.Consumer,不好区分
加入 上下文对象的
displayName
14.2.4 displayName
const MyContext = React.createContext()
const ColorContext = React.createContext()
MyContext.displayName = 'MyContext'
ColorContext.displayName = 'ColorContext'
14.3 常见应用场景解读
- 共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
- 配合
react hooks中的useReducer可以实现轻量的redux
十五、高阶组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。
HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
15.1 理解高阶组件、作用及其特点
一个高阶组件只是一个包装了另外一个 React 组件的 函数。 这种形式通常实现为一个函数,本质上是一个类工厂。
.实现了对原有组件的增强和优化。
可以对原有组件中的state, props和逻辑执行增删改操作, 一般用于代码重用和组件增强优化
15.2 高阶组件语法详解
我们想要我们的组件通过自动注入一个版权信息
15.2.1 组件嵌套
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './10_hoc/01App_more_use' // 普通多次调用组件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/10_hoc/01App_more_use.jsx
// src/10_hoc/01App_more_use.jsx
import React, { Component } from 'react';
class Footer extends Component {
render () {
return (
<footer>公共页面底部</footer>
)
}
}
const Page3 = () => {
return (
<div>
第三个页面
<Footer />
</div>
)
}
class Page2 extends Component {
render () {
return (
<div>
第二个页面
<Footer />
</div>
)
}
}
class Page1 extends Component {
render () {
return (
<div>
第一个页面
<Footer />
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<Page1></Page1>
<Page2></Page2>
<Page3></Page3>
</div>
);
}
}
export default App;
通过
Footer组件可以复用jsx代码,但是其余的业务逻辑代码显得无能为力,可以通过高阶组件来实现
15.2.2 高阶组件
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './10_hoc/01App_more_use' // 普通多次调用组件
import App from './10_hoc/02App_more_use_hoc' // 高阶组件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/10_hoc/02App_more_use_hoc.jsx
// src/10_hoc/02App_more_use_hoc.jsx
import React, { Component } from 'react';
// class Footer extends Component {
// render () {
// return (
// <footer>公共页面底部</footer>
// )
// }
// }
const withFooter = (Com, obj) => {
// {...this.props} 传递原有的组件的属性
return class extends Component {
// 如果需要复用业务逻辑: 状态来源于 传入的组件,不要在高阶组件内自己定义初始化数据,
// state = { couunt: 0} 错误的
state = {
count: obj.count
}
componentDidMount () {
// https://zh-hans.legacy.reactjs.org/docs/strict-mode.html#%E7%A1%AE%E4%BF%9D%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84-state-ensuring-reusable-state
// React 18 为严格模式引入了一个全新的仅用于开发环境的检查操作。
// 每当第一次安装组件时,这个新的检查将自动卸载并重新安装每个组件,并在第二次挂载时恢复之前的 state。
console.log('666') // 开启严格模式,执行两组打印
}
add = () => {
console.log('add')
this.setState({
count: this.state.count + 1
})
// obj.count += 1
}
render () {
return (
<>
<Com add = { this.add.bind(this) } count = { this.state.count } {...this.props}/>
<footer>公共页面底部 </footer>
</>
)
}
}
}
let Page3 = (props) => {
return (
<div>
第三个页面 - { props.count }
<button onClick={ props.add }>加1</button>
</div>
)
}
Page3 = withFooter(Page3, { count: 100 })
class Page2 extends Component {
// state = {
// count: 10
// }
render () {
console.log('999')
return (
<div>
第二个页面 - { this.props.count }
<button onClick={ this.props.add }>加1</button>
</div>
)
}
}
Page2 = withFooter(Page2, { count: 10 })
class Page1 extends Component {
render () {
return (
<div>
第一个页面 - { this.props.count }
<button onClick={ this.props.add }>加1</button>
</div>
)
}
}
Page1 = withFooter(Page1, { count: 1})
class App extends Component {
render() {
return (
<div>
<Page1></Page1>
<Page2></Page2>
<Page3></Page3>
</div>
);
}
}
export default App;
先执行了 组件的生命周期,后执行了高阶组件的生命周期
但是以后再复用组件的业务时,可以选用函数式组件的自定义hooks
15.3 常见应用场景解读
1.需要代码重用时, react如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复
2.需要组件增强优化时, 比如我们在项目中使用的组件有些不是自己写的, 而是从网上撸下来的,但是第三方写的组件可能比较复杂, 有时不能完全满足需求, 但第三方组件不易修改, 此时也可以用高阶组件,在不修改原始组件的前提下, 对组件添加满足实际开发需求的功能
3.可以对原有组件中的state, props和逻辑执行增删改操作, 一般用于代码重用和组件增强优化
4.也可以用来替换 mixins 混入
父组件和高阶组件有什么区别?
- 首先从逻辑的执行流程上来看,高阶组件确实和父组件比较相像
- 但是
高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;- 如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。
如果高阶组件和 调用的组件拥有相同的业务逻辑,生命周期先执行高阶组件,后执行调用组件
十六、ref
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
16.1 ref访问DOM
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './11_ref/01App_ref_dom' // ref访问DOM节点
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/11_ref/01App_ref_dom.jsx
// src/11_ref/11_ref/01App_ref_dom.jsx
import React, { Component } from 'react';
function Child () {
// 在函数式组件中不可以使用 string 类型的ref
// 在函数式组件中 可以使用 React.createRef()
// 但是在函数式组件中最好使用 useRef()
// const btn4Ref = React.createRef() // 对
const btn4Ref = React.useRef()
return (
<div>
<button ref = { btn4Ref }>btn4</button>
<button onClick={ () => {
console.log('btn4', btn4Ref.current)
}}>获取函数式组件的DOM</button>
</div>
)
}
class App extends Component {
btnRef = React.createRef()
render() {
return (
<div>
<button id='btnJs'>btn1</button>
<button ref='btnString'>btn2</button>
<button ref= { this.btnRef }>btn3</button>
<button onClick={ () => {
console.log('btn1', document.getElementById('btnJs'))
console.log('btn2', this.refs.btnString)
console.log('btn3', this.btnRef.current)
}}>获取DOM</button>
<Child />
</div>
);
}
}
export default App;
ref 访问组件
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './11_ref/01App_ref_dom' // ref访问DOM节点
import App from './11_ref/02App_ref_Com' // ref访问子组件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
// src/11_ref/02App_ref_Com.jsx
import React, { Component } from 'react';
// 使用React.forwardRef 先包裹子组件 -- 高阶组件
const ChildFunction = React.forwardRef(() => {
const test = () => {
console.log('hahaha')
}
return (
<div>函数式 组件</div>
)
})
class ChildClass extends Component {
state = {
count: 10
}
add = () => {
this.setState({
count: this.state.count + 10
})
}
render () {
return (
<div>class 组件 - { this.state.count }</div>
)
}
}
// 如果子组件是 类组件,那么可以通过ref获取到子组件的实例。
// 如果子组件是 函数式组件,那么不要直接给它设置ref
// 如果非要设置,那么请使用React.forwardRef 先包裹子组件
class App extends Component {
classRef = React.createRef()
functionRef = React.createRef()
render() {
return (
<div>
<ChildClass ref = { this.classRef }></ChildClass>
<ChildFunction ref = { this.functionRef }></ChildFunction>
<button onClick={() => {
console.log(this.classRef)
console.log(this.classRef.current)
console.log(this.classRef.current.state.count)
this.classRef.current.add()
}}>获取类组件的实例</button>
<button onClick={() => {
console.log(this.functionRef)
console.log(this.functionRef.current) // null
}}>获取函数式组件</button>
</div>
);
}
}
export default App;
如果在上述案例中,在
FunCom组件中上使用ref,发现报了警告信息Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
其原因是,函数式组件使用ref,必须使用React.forwardRef()方法二次包装
16.2 详解ref转发
Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './11_ref/01App_ref_dom' // ref访问DOM节点
// import App from './11_ref/02App_ref_Com' // ref访问子组件
import App from './11_ref/03App_Ref_Com_forward' // ref 转发
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
src/11_ref/03App_Ref_Com.jsx
// src/11_ref/03App_Ref_Com_forward.jsx
import React, { Component } from 'react';
// 使用React.forwardRef 先包裹子组件 -- 高阶组件
// ref的转发 ---- 将父组件的ref值赋值给子组件的DOM元素
const ChildFunction = React.forwardRef((props, ref) => { // forwardRef 组件内就有第二个参数ref
const test = () => {
console.log('hahaha')
}
return (
<div ref = { ref }>函数式 组件</div>
)
})
class ChildClass extends Component {
state = {
count: 10
}
add = () => {
this.setState({
count: this.state.count + 10
})
}
render () {
return (
<div>class 组件 - { this.state.count }</div>
)
}
}
// 如果子组件是 类组件,那么可以通过ref获取到子组件的实例。
// 如果子组件是 函数式组件,那么不要直接给它设置ref
// 如果非要设置,那么请使用React.forwardRef 先包裹子组件
class App extends Component {
classRef = React.createRef()
functionRef = React.createRef()
render() {
return (
<div>
<ChildClass ref = { this.classRef }></ChildClass>
<ChildFunction ref = { this.functionRef }></ChildFunction>
<button onClick={() => {
console.log(this.classRef)
console.log(this.classRef.current)
console.log(this.classRef.current.state.count)
this.classRef.current.add()
}}>获取类组件的实例</button>
<button onClick={() => {
console.log(this.functionRef)
console.log(this.functionRef.current) // null
}}>获取函数式组件</button>
</div>
);
}
}
export default App;
16.3 使用ref注意事项
- 当
ref属性用于 HTML 元素时,使用React.createRef()创建的ref接收底层 DOM 元素作为其current属性。 - 当
ref属性用于自定义 class 组件时,ref对象接收组件的挂载实例作为其current属性。 - 你不能在函数组件上使用
ref属性,因为他们没有实例。如果非要使用,实际上是转发ref(父组件中获取到了子组件的某个DOM,并且可以继续操作DOM)
16.4 非受控组件
只读的表单元素,比如<input type = "file" />
参考 11.3章节
上传图片前预览的操作
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './11_ref/01App_ref_dom' // ref访问DOM节点
// import App from './11_ref/02App_ref_Com' // ref访问子组件
// import App from './11_ref/03App_Ref_Com_forward' // ref 转发
import App from './11_ref/04App_file' // 上传图片预览
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
// src/11_ref/05App_file.jsx
import React, { Component } from 'react';
// input text DOM.value
// input radio DOM.checked
// input file DOM.files 获取选中的文件,以数组的形式展示,每一个文件即为一个元素
class App extends Component {
fileRef = React.createRef()
linkRef = React.createRef()
state = {
url: '',
link: ''
}
selectFile = () => {
const file = this.fileRef.current.files[0]
const that = this
console.log(file)
// 依据文件信息 ,读取文件信息,讲图片文件以base64的形式输出
// 读取文件信息 - 文件阅读器 - 以base64格式读取
const reader = new FileReader()
reader.readAsDataURL(file) // 输出base64
// 读取图片是一个异步操作 ---- 图片是一像素一像素渲染的
reader.onload = function () { // 需要的this指向,reader.onload 内要通过 result 读取结果信息,this指向的是reader阅读器
console.log(this.result)
that.setState({
url: this.result
})
}
}
upload = () => {
/**
* axios.post('', {
* img: this.state.url,
* link: this.state.link
* })
*/
}
render() {
return (
<div>
<input type="text" ref={ this.linkRef } placeholder='点击链接'/>
<input ref = { this.fileRef } type="file" onChange={ this.selectFile }/>
<img src={ this.state.url } alt="" />
<button onClick={ this.upload }>上传</button>
</div>
);
}
}
export default App;