国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

中文輸入法與React文本輸入框的問題與解決方案

shevy / 1245人閱讀

摘要:本文的目的是希望能針對這個問題提供一些說明現在暫時性的解決方案。完全不會有問題的只有一個瀏覽器,就是上面注釋中所說的已經實作出的,上可能根本不需要任何解決方案,它的輸入法編輯器是獨立于瀏覽器上的文本輸入框之外的。

問題來源是來自這個React官方存儲庫的issue #3926,與這個議題關聯的有很多其他的issue,來自許多項目,有些是與React相關,有些則是vue或其它JS套件。也已經有其他的項目是專注于解決這個問題,例如react-composition,不過它是一個使用ES5語法的React組件。在其他的討論區上也有類似的問題與解答。本文的目的是希望能針對這個問題提供一些說明、現在暫時性的解決方案。

下圖為目前解決React中"Controlled"(受控制的)input元件的演示,可以到這里去測試:

注意事項: 目前的解決方案我認為是暫時性的,結果都放在這個github庫上。這要分為"Controlled"(受控制的)與"Uncontrolled"(不受控制的)兩個種類的組件,影響的主要是input與textarea兩個組件,輸入法(IME, input method editor)的問題,不只會發生在中文,同樣的在日文、韓文或其它使用輸入法的語言應該都有同樣問題。

問題何來

React組件主要使用onChange人造事件,作為文本輸入框(input)或文字輸入區(textarea)觸發文字輸入時的事件,這個事件用起來很直覺,理應當是如此。但onChange在瀏覽器上,只要在這個文本輸入框上,有任何的鍵盤動作它都會觸發,也就是如果你是使用了中文、日文、韓文輸入法(IME),不論是哪一種,拼音的、筆劃的還是其他的,只要有按下一個鍵盤的動作,就會觸發一次瀏覽器上這個元素的change事件,對于原本就使用鍵盤上的英文字符作為輸入的語言來說,這沒什么太大的問題,但對于要使用輸入法的語言用戶來說,不停的觸發change事件,可能會造成程序功能上的運行邏輯問題。

舉出一個實際的應用情況,一個使用React撰寫的搜索計算機書籍的功能,用戶可以在文本輸入框里輸入要搜索的書名,程序中是利用onChange事件觸發,進行比對數據庫中的書籍標題,當你想搜索一本名為"林哥的Java教程",第一個字為"林",拼音輸入法需要輸入"lin"三個鍵盤上的字符,在"林"這個字從輸入法編輯器中加到真正的input元素前,onChange已經捕捉到"lin"三個字符,在列表中已搜索出一大堆有關"linux"的書籍。細節就不說了,還有可能對字符數量的的檢查之類的問題。不過,這是正確的程序運作邏輯嗎?很明顯的這是一個大問題。

當然,你也可以用對中文字詞檢查的修正方式,或是干脆不要用change事件,改用其他按鈕觸發之類的事件來作這事情,或是不要用React中的"Controlled"(受控制的)input或textare組件,但這會局限住在程序開發應用上的自由,要如何選擇就看你自己了,是不要使用它還是想辦法正視問題來解決它。

網頁上的DOM元素與"Uncontrolled"(不受控制的)的組件

這個問題在瀏覽器中,早就已經有了可應對的解決方法,DOM事件中有一組額外的CompositionEvent(組成事件)可以輔助開發者,它可以在可編輯的DOM元素上觸發,主要是input與textarea上,所以可以用來輔助解決change事件的輸入法問題。CompositionEvent(組成事件)共有三個事件,分別為compositionstartcompositionupdatecompositionend,它們代表的是開始進行字的組成、刷新與結束,也就是代表開始以輸入法編輯器來組合鍵盤上的英文字符,選字或刷新字的組合,到最后輸出字到真實DOM中的文本輸入框中,實務上每個中文字在輸入時,compositionstartcompositionend都只會會被觸發一次,而compositionupdate則是有可能多次觸發。

藉由CompositionEvent的輔助來解決的方式,也就是說在網頁上的input元素,可以利用CompositionEvent作為一個信號,如果正在使用IME輸入中文時,change事件中的代碼就先不要運行,等compositionend觸發時,接著的change事件才可以運行其中的代碼,運作的原理就是這樣簡單而已。

在React應用中,如果是一個"Uncontrolled"(不受控制的)的input組件,它與網頁上真實DOM中的input元素的事件行為無差異,也就是說,直接使用CompositionEvent的解決方式,就可以解決這個輸入法的問題,以下面的代碼為例子:

// @flow
import React from "react"

const Cinput = (props: Object) => {
  // record if is on Composition
  let isOnComposition: boolean = false

  const handleComposition = (e: KeyboardEvent) => {
    if (e.type === "compositionend") {
      // composition is end
      isOnComposition = false
    } else {
      // in composition
      isOnComposition = true
    }
  }

  const handleChange = (e: KeyboardEvent) => {
    // only when onComposition===false to fire onChange
    if (e.target instanceof HTMLInputElement && !isOnComposition) {
      props.onChange(e)
    }
  }

  return (
    
  )
}

export default Cinput

上面這是一個典型的"Uncontrolled"(不受控制的)input組件,主要是它不用value這個屬性。但如果它有來自上層組件的value屬性與值,也就是上層組件用props傳遞給它value屬性的值,就成了"Controlled"(受控制的)組件,它的事件整個模式就會與網頁上的真實DOM中的input元素不一樣,這后面再說明。

這個解決方案在幾乎所有能支持CompositionEvent的瀏覽器(IE9以上)都可以運行得很好,不過在Google Chrome瀏覽器在2016年的版本53之后,更動了changecompositionend的觸發順序,所以需要針對Chrome瀏覽器調整一下,如果是在Chrome瀏覽器中觸發compositionend時,也要運行一次在原本在change要運行的代碼,就改成這樣而已。下面在上個代碼中的handleComposition函數中,多加了偵測是否為Chrome瀏覽器,與觸發原本的onChange方法代碼,修改過的代碼如下:

// detect it is Chrome browser?
const isChrome = !!window.chrome && !!window.chrome.webstore

const handleComposition = (e: KeyboardEvent) => {
  if (e.type === "compositionend") {
    // composition is end
    isOnComposition = false

    // fixed for Chrome v53+ and detect all Chrome
    // https://chromium.googlesource.com/chromium/src/
    // +/afce9d93e76f2ff81baaa088a4ea25f67d1a76b3%5E%21/
    if (e.target instanceof HTMLInputElement && !isOnComposition && isChrome) {
      // fire onChange
      props.onChange(e)
    }
  } else {
    // in composition
    isOnComposition = true
  }
}

"Uncontrolled"(不受控制的)input或textarea組件,解決方式就是這么簡單而已,利用CompositionEvent過濾掉不必要的change事件。

注: 其它的解決方式還有,像InputEvent中有一個isComposing屬性,它也可以作為偵測目前是否正在進行輸入法的組字工作,但InputEvent事件目前只有Firefox中可以用,看起來沒什么前景。另外,W3C新提出的IME API或許是一個未來較佳的解決方案,但目前只有IE11 有實作,其他瀏覽器品牌都沒有。

"Controlled"(受控制的)的組件

在React應用中,使用"Controlled"(受控制的)的input或textarea組件是另一回事,它會開始復雜起來。

"Controlled"(受控制的)的組件并不是只有加上value這個屬性這么簡單,input或textarea組件所呈現的值,主要會來自state,state有可能是上層組件的,利用props一層層傳遞過來的,或是這個組件中本身就有的state,直接賦給在這個組件中的render中的input或textarea組件。也就是說,input最后呈現的文字如果要進行改變,就需要改變到組件(不論在何處)的state,要改變state只有透過setState方法,而setState方法有可能是個異步(延時)運行的情況。

把這整個流程串接在一起后,我相信事件觸發的不連續情況會變得很嚴重,需要對不同情況下作測試與評估。目前我所作的測試還只是最基本的組件運用而已,復雜的組件情況還沒有開始進行。因為state有很多種用途,有時候內部使用,有時候要對外部用戶輸入介面的事件,或是有時候要對服務器端的數據接收或傳送,不論是不是要使用Redux、MobX或Flux之類的state容器函數庫或框架,最終要進行重新渲染的工作,還是得調用React中的setState方法才行。

在基本的測試時,我發現"Controlled"(受控制的)的input組件,它不僅事件觸發不連續的情況嚴重,而且有可能在不同瀏覽器上會有不同的結果。完全不會有問題的只有一個瀏覽器,就是上面注釋中所說的已經實作出IME API的IE11,IE11上可能根本不需要任何解決方案,它的輸入法編輯器是獨立于瀏覽器上的文本輸入框之外的。

目前已測試的結果是有三種情況,"Chrome, Opera, IE, Edge"為一種,"Firefox"為一種,"Safari"為一種。我為這三種情況分別寫了不同的解決方式的代碼,但這個事件觸發的不連續情況,現在無法有一致性的解決方案,我只能推測這大概可能是React內部設計的問題。

不論是三種的那一種解決方案,有一個重點是你不能像上面的一般性解決方案,阻擋change事件時要運行的代碼,也就是阻擋setState變動state值,因為只要一經阻擋,input組件的value值就賦不到值,而且也不會觸發重新渲染。所以你只能讓change事件不斷觸發,就像往常一樣。

那么要如何解決程序邏輯運作的問題?

我使用了另一個內部的state對象中的值,稱為innerValue,它是對比在input組件上不斷因觸發change事件而輸入的值,稱為inputValueinnerValue是個會經過CompositionEvent修正過的值,所以它永遠不會帶有在輸入法組字過程的字符串值。

這個解決方案,是一個"掛羊頭賣狗肉"的用法,不論用戶在input組件如何輸入,輸入的過程都會改變inputValue而已,inputValue是一個暫存與呈現用的值,最終用來進行程序邏輯運算的是innerValue。以最一開始的例子來說,用戶輸入"林哥的Java教程",在一開始的"林"字輸入時,inputValue是從"lin"到輸入完成變為"林",而innerValue是在輸入期間是空字符串值,輸入完成才會變為"林"。所以,搜索功能可以用innerValue來作為運算的依據,用這個值來搜索對應的數據,這才是正確的運算邏輯,因為innerValue才是真正的不帶輸入法組字過程的值。

大致上說明一下解決方式的代碼,首先它有兩個在這個模塊作用域中的全局變量,一個用來記錄是否在輸入法的組字過程中,另一個是給專給Safari瀏覽器用的:

// if now is in composition session
let isOnComposition = false

// for safari use only, innervalue can"t setState when compositionend occurred
let isInnerChangeFromOnChange = false

在專門處理change事件的handleChange方法中,判斷isInnerChangeFromOnChange這一段是專門為了解決Safari瀏覽器的問題所寫,Safari瀏覽器的行為是CompositionEvent在觸發時,其中的event.target.value居然是組字過程中的英文字符,而不是觸發這個事件的input元素的所有字符串,這也是特別怪異的地方,所以才會利用在compositionend后會再觸發一次change的特性,在這里刷新innerValue

后面的代碼,是代表在輸入法的組字過程中,setState方法使用的差異,在組字過程中(isOnComposition === true)的話,只會更動inputValue值,而不會更動到innerValue的值,這對應了上述所說的一個運作過程,一般的輸入鍵盤上的字符時不會有輸入法的問題,則是兩個值一并更動。代碼如下:

handleChange = (e: Event) => {
   // console.log("change type ", e.type, ", target ", e.target, ", target.value ", e.target.value)

  // Flow check
  if (!(e.target instanceof HTMLInputElement)) return

  if (isInnerChangeFromOnChange) {
    this.setState({ inputValue: e.target.value, innerValue: e.target.value })
    isInnerChangeFromOnChange = false
    return
  }

  // when is on composition, change inputValue only
  // when not in composition change inputValue and innerValue both
  if (!isOnComposition) {
    this.setState({
      inputValue: e.target.value,
      innerValue: e.target.value,
    })
  } else {
    this.setState({ inputValue: e.target.value })
  }
}

在專門處理composition事件的handleComposition方法中,主要是為了在compositionend觸發時,進行刷新innerValue所撰寫的一些代碼。在第一種情況時,也就是在Chrome, IE, Edge, Opera瀏覽器時,只需要直接用e.target.value刷新innerValue即可。在第二種情況是Firefox,它不知道為什么會掉值,所以還需要幫它再一并刷新innerValue一次。第三種情況,上面有說過了,特別的怪異情況,所以對innerValue的刷新改到compositionend之后的那個change事件去作了。代碼如下:

handleComposition = (e: Event) => {
   // console.log("type ", e.type, ", target ", e.target, ",target.value ", e.target.value, ", data", e.data)

   // Flow check
  if (!(e.target instanceof HTMLInputElement)) return

  if (e.type === "compositionend") {
    // Chrome is ok for only setState innerValue
    // Opera, IE and Edge is like Chrome
    if (isChrome || isIE || isEdge || isOpera) {
      this.setState({ innerValue: e.target.value })
    }

    // Firefox need to setState inputValue again...
    if (isFirefox) {
      this.setState({ innerValue: e.target.value, inputValue: e.target.value })
    }

    // Safari think e.target.value in composition event is keyboard char,
    //  but it will fire another change after compositionend
    if (isSafari) {
       // do change in the next change event
      isInnerChangeFromOnChange = true
    }

    isOnComposition = false
  } else {
    isOnComposition = true
  }
}

注: 目前這個暫時的解決方式,其方式并不是參考自react-composition項目,解決方式雖然有些類似,但react-composition用的是ES5的React工廠樣式組件語法,我對這種語法并不熟悉。在寫這篇文檔時,才仔細看了一下react-composition的代碼,只能說它的作者實際上也有測試過這個問題,也知道只有用另一個state中的值才能解決這問題。

總結

如果你是使用"Uncontrolled"(不受控制的)的組件,那么解決方法很簡單,就如同上面所說的,像一般的網頁上的DOM元素的解決方式即可。

但對于"Controlled"(受控制的)的組件來說,目前的解決方案是一種try-and-error(試誤法)的暫時性解決方案,我目前只能按照已測試的平臺與瀏覽器去修正,沒測過的瀏覽器與平臺,就不得而知了。

關于這個"Controlled"(受控制的)的組件的事件觸發,目前看到有在不同瀏覽器上的事件觸發不連續情況,我也有發一個議題(Issue)給React官方。或許比較好的治本方案,是需要從state更動方式的內部代碼,或是人造事件觸發的順序,進行一些調整,這超出我的能力范圍,就有待開發團隊的回應了。

最后,如果你正好有需要到這個功能,或是你認為這個功能有需要,你可以幫忙測試看看或是提供一些建議。我已經把所有的代碼、演示、線上測試、解決方案都集中到這個Github庫的react-compositionevent中。或許你現在需要一個解決方案,你可以用里面目前的暫時性解決方式試試也可以。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88070.html

相關文章

  • 學習React系列2-[解讀]Thinking in React

    摘要:擴展單一職責原則又稱單一功能原則,面向對象五個基本原則之一。馬丁表示此原則是基于湯姆狄馬克和的著作中的內聚性原則發展出的。 [解讀]Thinking in React 原文:http://facebook.github.io/react/docs/thinking-in-react.html 前言 Thought is the seed of action 這是放置在官方的QUICK ...

    tomorrowwu 評論0 收藏0
  • 翻譯 | 玩轉 React 表單 —— 受控組件詳解

    摘要:受控輸入框只會顯示通過傳入的數據。例如,數組中的元素將會渲染三個單選框或復選框。屬性接收一個布爾值,用來表示組件是否應該被渲染成選中狀態。 原文地址:React.js Forms: Controlled Components 原文作者:Loren Stewart 譯者:小 B0Y 校對者:珂珂君 本文涵蓋以下受控組件: 文本輸入框 數字輸入框 單選框 復選框 文本域 下拉...

    big_cat 評論0 收藏0
  • [ 一起學React系列 -- 5 ] 如何優雅得使用表單控件

    摘要:假如我們從后臺拉取一個數據要填入輸入框,那么必須得使用受控組件,因為非受控組件只能被用戶輸入。不影響正常輸入填充該輸入框的默認值,此時不顯示內容。 網頁中使用的form表單大家肯定都再熟悉不過了,它主要作用是用來收集和提交信息。React中的表單組件與我們普通的Html中的表單及其表現形式沒有什么不同,所以如何使用表單我覺得再拿出來說可能是畫蛇添足、毫無意義。不過再怎么樣也不能辜負大家...

    Charlie_Jade 評論0 收藏0
  • 基于 Vue 的移動端富文本編輯器 vue-quill-editor 實戰

    摘要:優秀的富文本編輯器有很多,比如,等,但并不是每個都能在移動端有很好的表現。是百度的老牌富文本編輯器,但界面有一股上世紀的感覺,官網最新的一條動態停留在。優秀的富文本編輯器有很多,比如:UEditor,wangEditor 等,但并不是每個都能在移動端有很好的表現。 我們暫且不討論移動端是否真的需要富文本,既然有這需求,就把它實現出來。 失敗的嘗試 正確的選擇是成功的開始,開發之前肯定要做一些...

    wing324 評論0 收藏0
  • 用Canvas實現文本編輯器(支持藝術字渲染動畫)

    摘要:項目中文字由進行渲染。待觸發時,取消中文輸入標記,將文字渲染到上。而其中一些有趣的細節實現如文本渲染,對中文筆畫分割實現有趣的動畫等并沒有描寫。 導言 目前富文本編輯器的實現主要有兩種技術方案:一個是利用contenteditable屬性直接對html元素進行編輯,如draft.js;另一種是代理textarea + 自定義div + 模擬光標實現。對于類似word的經典富文本編輯器,...

    OldPanda 評論0 收藏0

發表評論

0條評論

shevy

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<