React?Native?Modal?的封裝與使用實例詳解
背景
在使用 React Native(以下簡稱 RN ,使用版本為 0.59.5) 開發(fā) App 的過程中,有許許多多使用到彈窗控件的場景,雖然 RN 自帶了一個 Modal 控件,但是在使用過程中它有一些不太好的體驗和問題。
- Android 端的 Modal 控件無法全屏,也就是內(nèi)容無法從狀態(tài)欄處開始布局。
- ios 端的 Modal 控件的層級太高,是基于 window 的,如果在 Modal 中打開一個新的 ViewController 界面的時候,將會被 Modal 控件給覆蓋住,同時 ios 的 Modal 控件只能彈出一個。
針對上面所發(fā)現(xiàn)的問題,我們需要對 RN 的 Modal 控件整體做一個修改和封裝,以便于在使用中可以應對各種不同樣的業(yè)務場景。
Android FullScreenModal 的封裝使用
針對第一個問題,查看了 RN Modal 組件在 Android 端的實現(xiàn),發(fā)現(xiàn)它是對 Android Dialog 組件的一個封裝調用,那么假如我能實現(xiàn)一個全屏展示的 Dialog,那么是不是在 RN 上也就可以實現(xiàn)全屏彈窗了。
Android 原生實現(xiàn)全屏 Dialog
FullScreenDialog 主要實現(xiàn)代碼如下
public class FullScreenDialog extends Dialog { private boolean isDarkMode; private View rootView; public void setDarkMode(boolean isDarkMode) { this.isDarkMode = isDarkMode; } public FullScreenDialog(@NonNull Context context, @StyleRes int themeResId) { super(context, themeResId); } @Override public void setContentView(@NonNull View view) { super.setContentView(view); this.rootView = view; } @Override public void show() { super.show(); StatusBarUtil.setTransparent(getWindow()); if (isDarkMode) { StatusBarUtil.setDarkMode(getWindow()); } else { StatusBarUtil.setLightMode(getWindow()); } AndroidBug5497Workaround.assistView(rootView, getWindow()); } }
在這里主要起作用的是 StatusBarUtil.setTransparent(getWindow());
方法,它的主要作用是將狀態(tài)欄背景透明,并且讓布局內(nèi)容可以從 Android 狀態(tài)欄開始。
/** * 使狀態(tài)欄透明,并且是從狀態(tài)欄處開始布局 */ @TargetApi(Build.VERSION_CODES.KITKAT) private static void transparentStatusBar(Window window) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { View decorView = window.getDecorView(); int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; decorView.setSystemUiVisibility(option); window.setStatusBarColor(Color.TRANSPARENT); } else { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } }
這里需要注意的是,該方法只有在 Android 4.4 以上才會有效果,不過如今已經(jīng)是 9012 年了,主流 Android 用戶使用的版本應該沒有低于 Android 4.4 了吧。
封裝給 RN 進行相關的調用
Android 原生部分實現(xiàn)
有了 FullScreenDialog ,下一步就是封裝組件給 RN 進行調用了,這里主要的步驟就是參考 RN Modal 的 Android 端實現(xiàn),然后替換其中的 Dialog 為 FullScreenDialog,最后封裝給 RN 進行調用。
public class FullScreenModalManager extends ViewGroupManager<FullScreenModalView> { @Override public String getName() { return "RCTFullScreenModalHostView"; } public enum Events { ON_SHOW("onFullScreenShow"), ON_REQUEST_CLOSE("onFullScreenRequstClose"); private final String mName; Events(final String name) { mName = name; } @Override public String toString() { return mName; } } @Override @Nullable public Map getExportedCustomDirectEventTypeConstants() { MapBuilder.Builder builder = MapBuilder.builder(); for (Events event : Events.values()) { builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); } return builder.build(); } @Override protected FullScreenModalView createViewInstance(ThemedReactContext reactContext) { final FullScreenModalView view = new FullScreenModalView(reactContext); final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class); view.setOnRequestCloseListener(new FullScreenModalView.OnRequestCloseListener() { @Override public void onRequestClose(DialogInterface dialog) { mEventEmitter.receiveEvent(view.getId(), Events.ON_REQUEST_CLOSE.toString(), null); } }); view.setOnShowListener(new DialogInterface.OnShowListener() { @Override public void onShow(DialogInterface dialog) { mEventEmitter.receiveEvent(view.getId(), Events.ON_SHOW.toString(), null); } }); return view; } @Override public LayoutShadowNode createShadowNodeInstance() { return new FullScreenModalHostShadowNode(); } @Override public Class<? extends LayoutShadowNode> getShadowNodeClass() { return FullScreenModalHostShadowNode.class; } @Override public void onDropViewInstance(FullScreenModalView view) { super.onDropViewInstance(view); view.onDropInstance(); } @ReactProp(name = "autoKeyboard") public void setAutoKeyboard(FullScreenModalView view, boolean autoKeyboard) { view.setAutoKeyboard(autoKeyboard); } @ReactProp(name = "isDarkMode") public void setDarkMode(FullScreenModalView view, boolean isDarkMode) { view.setDarkMode(isDarkMode); } @ReactProp(name = "animationType") public void setAnimationType(FullScreenModalView view, String animationType) { view.setAnimationType(animationType); } @ReactProp(name = "transparent") public void setTransparent(FullScreenModalView view, boolean transparent) { view.setTransparent(transparent); } @ReactProp(name = "hardwareAccelerated") public void setHardwareAccelerated(FullScreenModalView view, boolean hardwareAccelerated) { view.setHardwareAccelerated(hardwareAccelerated); } @Override protected void onAfterUpdateTransaction(FullScreenModalView view) { super.onAfterUpdateTransaction(view); view.showOrUpdate(); } }
在這里有幾點需要注意的
- 由于 RN Modal 已經(jīng)存在了 onShow 和 onRequestClose 回調,這里不能再使用這兩個命名,所以這里改成了 onFullScreenShow 和 onFullScreenRequstClose,但是在 js 端還是重新命名成 onShow 和 onRequestClose ,所以在使用過程中還是沒有任何變化
- 增加了 isDarkMode 屬性,對應上面的狀態(tài)欄字體的顏色
- 增加了 autoKeyboard 屬性,根據(jù)該屬性判斷是否需要自動彈起軟件盤
其他的一些屬性和用法也就跟 RN Modal 的一樣了。
JS 部分實現(xiàn)
在 JS 部分,我們只需要 Android 的實現(xiàn)就好了,ios 還是沿用原來的 Modal 控件。這里參照 RN Modal 的 JS 端實現(xiàn)如下
import React, {Component} from "react"; import {requireNativeComponent, View} from "react-native"; const FullScreenModal = requireNativeComponent('RCTFullScreenModalHostView', FullScreenModalView) export default class FullScreenModalView extends Component { _shouldSetResponder = () => { return true; } render() { if (this.props.visible === false) { return null; } const containerStyles = { backgroundColor: this.props.transparent ? 'transparent' : 'white', }; return ( <FullScreenModal style={{position: 'absolute'}} {...this.props} onStartShouldSetResponder={this._shouldSetResponder} onFullScreenShow={() => this.props.onShow && this.props.onShow()} onFullScreenRequstClose={() => this.props.onRequestClose && this.props.onRequestClose()}> <View style={[{position: 'absolute', left: 0, top: 0}, containerStyles]}> {this.props.children} </View> </FullScreenModal> ) } }
使用 RootSiblings 封裝 Modal
針對第二個問題,一種方法是通過 ios 原生去封裝實現(xiàn)一個 Modal 控件,但是在 RN 的開發(fā)過程中,發(fā)現(xiàn)了一個第三方庫 react-native-root-siblings , 它重寫了系統(tǒng)的 AppRegistry.registerComponent 方法,當我們通過這個方法注冊根組件的時候,替換根組件為我們自己的實現(xiàn)的包裝類。包裝類中監(jiān)聽了目標通知 siblings.update,接收到通知就將通知傳入的組件視圖添加到包裝類頂層,然后進行刷新顯示。通過 RootSiblings 也可以實現(xiàn)一個 Modal 組件,而且它的層級是在當前界面的最上層的。
實現(xiàn)界面 Render 相關
由于 RootSiblings 的實現(xiàn)是通過將組件添加到它注冊到根節(jié)點中的,并不直接通過 Component 的 Render 進行布局,而 RN Modal 的顯示隱藏是通過 visible 屬性進行控制,所以在 componentWillReceiveProps(nextProps)
中根據(jù) visible 進行相關的控制,部分實現(xiàn)代碼如下
render() { if (this.props.visible) { this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings()); } return null; } componentWillReceiveProps(nextProps) { const { onShow, animationType, onDismiss } = this.props; const { visible } = nextProps; if (!this.RootSiblings && visible === true) { // 表示從沒有到要顯示了 this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => { if (animationType === 'fade') { this._animationFadeIn(onShow); } else if (animationType === 'slide') { this._animationSlideIn(onShow); } else { this._animationNoneIn(onShow); } }); } else if (this.RootSiblings && visible === false) { // 表示顯示之后要隱藏了 if (animationType === 'fade') { this._animationFadeOut(onDismiss); } else if (animationType === 'slide') { this._animationSlideOut(onDismiss); } else { this._animationNoneOut(onDismiss); } } }
實現(xiàn) Modal 展示動畫相關
RN Modal 實現(xiàn)了三種動畫模式,所以這里在通過 RootSiblings 實現(xiàn) Modal 組件的時候也實現(xiàn)了這三種動畫模式,這里借助的是 RN 提供的 Animated 和 Easing 進行相關的實現(xiàn)
- ‘none’ 這種不必多說,直接進行展示,沒有動畫效果
- ‘fade’ 淡入淺出動畫,也就是透明度的一個變化,這里使用了 Easing.in 插值器使得效果更加平滑
- ‘slide’ 幻燈片的滑入畫出動畫,這是是組件 Y 方向位置的一個變化,這里使用了 Easing.in 插值器使得效果更加平滑
完整的一個 使用 RootSiblings 封裝 Modal 實現(xiàn)代碼如下
import React, { Component } from 'react'; import { Animated, Easing, Dimensions, StyleSheet, } from 'react-native'; import RootSiblings from 'react-native-root-siblings'; const { height } = Dimensions.get('window'); const animationShortTime = 250; // 動畫時長為250ms export default class ModalView extends Component { constructor(props) { super(props); this.state = { animationSlide: new Animated.Value(0), animationFade: new Animated.Value(0), }; } render() { if (this.props.visible) { this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings()); } return null; } componentWillReceiveProps(nextProps) { const { onShow, animationType, onDismiss } = this.props; const { visible } = nextProps; if (!this.RootSiblings && visible === true) { // 表示從沒有到要顯示了 this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => { if (animationType === 'fade') { this._animationFadeIn(onShow); } else if (animationType === 'slide') { this._animationSlideIn(onShow); } else { this._animationNoneIn(onShow); } }); } else if (this.RootSiblings && visible === false) { // 表示顯示之后要隱藏了 if (animationType === 'fade') { this._animationFadeOut(onDismiss); } else if (animationType === 'slide') { this._animationSlideOut(onDismiss); } else { this._animationNoneOut(onDismiss); } } } renderRootSiblings = () => { return ( <Animated.View style={[styles.root, { opacity: this.state.animationFade }, { transform: [{ translateY: this.state.animationSlide.interpolate({ inputRange: [0, 1], outputRange: [height, 0], }), }], }]}> {this.props.children} </Animated.View> ); } _animationNoneIn = (callback) => { this.state.animationSlide.setValue(1); this.state.animationFade.setValue(1); callback && callback(); } _animationNoneOut = (callback) => { this._animationCallback(callback); } _animationSlideIn = (callback) => { this.state.animationSlide.setValue(0); this.state.animationFade.setValue(1); Animated.timing(this.state.animationSlide, { easing: Easing.in(), duration: animationShortTime, toValue: 1, }).start(() => callback && callback()); } _animationSlideOut = (callback) => { this.state.animationSlide.setValue(1); this.state.animationFade.setValue(1); Animated.timing(this.state.animationSlide, { easing: Easing.in(), duration: animationShortTime, toValue: 0, }).start(() => this._animationCallback(callback)); } _animationFadeIn = (callback) => { this.state.animationSlide.setValue(1); this.state.animationFade.setValue(0); Animated.timing(this.state.animationFade, { easing: Easing.in(), duration: animationShortTime, toValue: 1, }).start(() => callback && callback()); } _animationFadeOut = (callback) => { this.state.animationSlide.setValue(1); this.state.animationFade.setValue(1); Animated.timing(this.state.animationFade, { easing: Easing.in(), duration: animationShortTime, toValue: 0, }).start(() => this._animationCallback(callback)); } _animationCallback = (callback) => { this.RootSiblings && this.RootSiblings.destroy(() => { callback && callback(); this.RootSiblings = undefined; }); } } const styles = StyleSheet.create({ root: { position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, }, });
使用 View 封裝 Modal
上面兩種 Modal 的封裝已經(jīng)能夠滿足絕大部分業(yè)務場景的需求,但是如果在 Modal 中需要打開新的界面(不創(chuàng)建新的 ViewController 和 Acticity ),并且 Modal 不進行隱藏的話,比如使用 react-navigation 跳轉頁面,那么上面實現(xiàn)的 Modal 層級會太高了。所以這里通過 View 去實現(xiàn)類似的一個 Modal 控件。它的實現(xiàn)代碼類似于上面的 RootSiblings 的實現(xiàn)。在 Render 中進行展示就好了,動畫也可以使用上面的實現(xiàn)
render() { return this._renderView() } _renderView = () => { if (this.state.visible) { return ( <Animated.View style={[styles.root, {opacity: this.state.animationFade}, { transform: [{ translateY: this.state.animationSlide.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }), }] }]}> {this.props.children} </Animated.View> ); } else { return null } }
整體 Modal 控件的封裝
相對于 RN Modal ,上面新增了三種 Modal 的實現(xiàn),為了整合整體的使用,所以需要對它們進行一個整體的封裝使用,通過制定 modalType 的方式,來指定我們需要它內(nèi)部的實現(xiàn),先上代碼
/** * @Author: linhe * @Date: 2019-05-12 10:11 * * 因為ios端同時只能存在一個Modal,并且Modal多次顯示隱藏會有很奇怪的bug * * 為了兼容ios的使用,這里需要封裝一個ModalView * * Android 依舊使用 React Native Modal 來進行實現(xiàn) * ios 的話采用 RootSiblings 配合進行使用 * * 這個是因為有的modal里面還需要跳轉到其他界面 * 這個時候主要要將該View放到最外邊的層級才可以 * * modalType:1 //表示使用Modal進行實現(xiàn) * 2 //表示使用RootSiblings進行實現(xiàn) * 3 //表示使用View進行實現(xiàn) * 注意:默認情況下 Android 使用的是1,ios使用的是2 * * 同時采用與 React Native Modal 相同的API */ 'use strict'; import React, {Component} from "react"; import {Animated, BackHandler, Platform, Easing, StyleSheet, Dimensions, Modal} from "react-native"; import PropTypes from 'prop-types' import RootSiblings from 'react-native-root-siblings'; import FullScreenModal from './FullScreenModal/FullScreenModal' const {height} = Dimensions.get('window') const animationShortTime = 250 //動畫時長為250ms const DEVICE_BACK_EVENT = 'hardwareBackPress'; export default class ModalView extends Component { static propTypes = { isDarkMode: PropTypes.bool, // false 表示白底黑字,true 表示黑底白字 autoKeyboard: PropTypes.bool, // 未知原因的坑,modal中的edittext自動彈起鍵盤要設置這個參數(shù)為true useReactModal: PropTypes.bool, // 是否使用 RN Modal 進行實現(xiàn) modalType: PropTypes.number // modalType 類型,默認 android 為 1,ios 為 2 }; static defaultProps = { isDarkMode: false, autoKeyboard: false, useReactModal: false, modalType: (Platform.OS === 'android' ? 1 : 2) // 默認 android 為1,ios 為2 }; constructor(props) { super(props); this.state = { visible: false, animationSlide: new Animated.Value(0), animationFade: new Animated.Value(0) }; } render() { const {modalType} = this.props if (modalType === 1) { //modal實現(xiàn) return this._renderModal() } else if (modalType === 2) { //RootSiblings實現(xiàn) this.RootSiblings && this.RootSiblings.update(this._renderRootSiblings()) return null } else { //View的實現(xiàn) return this._renderView() } } _renderModal = () => { const ModalView = this.props.useReactModal ? Modal : FullScreenModal return ( <ModalView transparent={true} {...this.props} visible={this.state.visible} onRequestClose={() => { if (this.props.onRequestClose) { this.props.onRequestClose() } else { this.disMiss() } }}> {this.props.children} </ModalView> ) } _renderRootSiblings = () => { return ( <Animated.View style={[styles.root, {opacity: this.state.animationFade}, { transform: [{ translateY: this.state.animationSlide.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }), }] }]}> {this.props.children} </Animated.View> ); } _renderView = () => { if (this.state.visible) { return ( <Animated.View style={[styles.root, {opacity: this.state.animationFade}, { transform: [{ translateY: this.state.animationSlide.interpolate({ inputRange: [0, 1], outputRange: [height, 0] }), }] }]}> {this.props.children} </Animated.View> ); } else { return null } } show = (callback) => { if (this.isShow()) { return } const {modalType, animationType} = this.props if (modalType === 1) { //modal this.setState({visible: true}, () => callback && callback()) } else if (modalType === 2) { //RootSiblings this.RootSiblings = new RootSiblings(this._renderRootSiblings(), () => { if (animationType === 'fade') { this._animationFadeIn(callback) } else if (animationType === 'slide') { this._animationSlideIn(callback) } else { this._animationNoneIn(callback) } }); // 這里需要監(jiān)聽 back 鍵 this._addHandleBack() } else { //view if (animationType === 'fade') { this.setState({visible: true}, () => this._animationFadeIn(callback)) } else if (animationType === 'slide') { this.setState({visible: true}, () => this._animationSlideIn(callback)) } else { this.setState({visible: true}, () => this._animationNoneIn(callback)) } // 這里需要監(jiān)聽 back 鍵 this._addHandleBack() } } disMiss = (callback) => { if (!this.isShow()) { return } const {modalType, animationType} = this.props if (modalType === 1) { //modal this.setState({visible: false}, () => callback && callback()) } else { //RootSiblings和View if (animationType === 'fade') { this._animationFadeOut(callback) } else if (animationType === 'slide') { this._animationSlideOut(callback) } else { this._animationNoneOut(callback) } // 移除 back 鍵的監(jiān)聽 this._removeHandleBack() } } isShow = () => { const {modalType} = this.props if (modalType === 1 || modalType === 3) { //modal和view return this.state.visible } else { //RootSiblings return !!this.RootSiblings } } _addHandleBack = () => { if (Platform.OS === 'ios') { return } // 監(jiān)聽back鍵 this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => { const {onRequestClose} = this.props if (onRequestClose) { onRequestClose() } else { this.disMiss() } return true }); } _removeHandleBack = () => { if (Platform.OS === 'ios') { return } this.handleBack && this.handleBack.remove() } _animationNoneIn = (callback) => { this.state.animationSlide.setValue(1) this.state.animationFade.setValue(1) callback && callback() } _animationNoneOut = (callback) => { this._animationCallback(callback); } _animationSlideIn = (callback) => { this.state.animationSlide.setValue(0) this.state.animationFade.setValue(1) Animated.timing(this.state.animationSlide, { easing: Easing.in(), duration: animationShortTime, toValue: 1, }).start(() => callback && callback()); } _animationSlideOut = (callback) => { this.state.animationSlide.setValue(1) this.state.animationFade.setValue(1) Animated.timing(this.state.animationSlide, { easing: Easing.in(), duration: animationShortTime, toValue: 0, }).start(() => this._animationCallback(callback)); } _animationFadeIn = (callback) => { this.state.animationSlide.setValue(1) this.state.animationFade.setValue(0) Animated.timing(this.state.animationFade, { easing: Easing.in(), duration: animationShortTime, toValue: 1, }).start(() => callback && callback()); } _animationFadeOut = (callback) => { this.state.animationSlide.setValue(1) this.state.animationFade.setValue(1) Animated.timing(this.state.animationFade, { easing: Easing.in(), duration: animationShortTime, toValue: 0, }).start(() => this._animationCallback(callback)); } _animationCallback = (callback) => { if (this.props.modalType === 2) {//RootSiblings this.RootSiblings && this.RootSiblings.destroy(() => { callback && callback() this.RootSiblings = undefined }) } else { //view this.setState({visible: false}, () => callback && callback()) } } } const styles = StyleSheet.create({ root: { position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, } });
這里主要通過 useReactModal 和 modalType 兩個屬性來控制我們所需要的實現(xiàn)
- modalType 表示 modal 內(nèi)部實現(xiàn)方式,1 表示使用層級較高的實現(xiàn),2 表示使用 RootSiblings 進行實現(xiàn),3 表示使用 View 進行實現(xiàn),當不進行指定的時候,默認 Android 為 1,ios 為 2
- useReactModal 主要針對 Android 端并且 modalType 為 1 的時候使用,true 表示使用 RN Modal,false 表示使用 FullScreenModal ,默認為 false
- 對外提供 show,disMiss,isShow 方法分別表示顯示彈窗,隱藏彈窗和判斷當前彈窗的狀態(tài),同時在 show 和 disMiss 方法調用的時候還添加了 callback 回調
其他
Android Back 鍵的注意
當 Modal 組件使用 RootSiblings 或者 View 實現(xiàn)的時候,它并沒有處理一個 Android 的返回鍵,所以對于這兩種實現(xiàn)的時候,要額外處理一個 Back 鍵的操作,這里借助了 RN BackHandler 這個了,如果 modalType 不為 1 的話,需要通過 BackHandler 去實現(xiàn)一個返回鍵的監(jiān)聽,然后通過 onRequestClose 屬性進行返回
const DEVICE_BACK_EVENT = 'hardwareBackPress'; _addHandleBack = () => { if (Platform.OS === 'ios') { return } // 監(jiān)聽back鍵 this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => { const {onRequestClose} = this.props if (onRequestClose) { onRequestClose() } else { this.disMiss() } return true }); } _removeHandleBack = () => { if (Platform.OS === 'ios') { return } this.handleBack && this.handleBack.remove() }
View 封裝 Modal 時候的注意
如果是 View 實現(xiàn)的 Modal 控件,那必須要注意它的一個層級,必須滿足它所處于整個界面布局的最外層,否則它可能會被其他組件所擋住,同時它的最大的顯示范圍取決于它的父 View 的顯示范圍。
最后
當然在最后還是要附上實現(xiàn)效果圖
再一次附上 Demo 地址:https://github.com/hzl123456/ModalViewDemo
到此這篇關于React Native Modal 的封裝與使用 的文章就介紹到這了,更多相關React Native Modal 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript中的useRef 和 useState介紹
這篇文章主要給大家分享的是 JavaScript中的useRef 和 useState介紹,下列文章,我們將學習 useRef 和 useState hook是什么,它們的區(qū)別以及何時使用哪個。 這篇文章中的代碼示例將僅涉及功能組件,但是大多數(shù)差異和用途涵蓋了類和功能組件,需要的朋友可以參考一下2021-11-11React Native之prop-types進行屬性確認詳解
本篇文章主要介紹了React Native之prop-types進行屬性確認詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12React render核心階段深入探究穿插scheduler與reconciler
這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-11-11react使用antd的上傳組件實現(xiàn)文件表單一起提交功能(完整代碼)
最近在做一個后臺管理項目,涉及到react相關知識,項目需求需要在表單中帶附件提交,怎么實現(xiàn)這個功能呢?下面小編給大家?guī)砹藃eact使用antd的上傳組件實現(xiàn)文件表單一起提交功能,一起看看吧2021-06-06React如何利用Antd的Form組件實現(xiàn)表單功能詳解
這篇文章主要給大家介紹了關于React如何利用Antd的Form組件實現(xiàn)表單功能的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04