2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

thisなきJS エラーあり

Posted at

はじめに

 JSでTODOアプリをつくり、そのやり方を教材として書き起こしていた。教材の仕様に沿ってアロー関数をつかったらうまく動作しなくなった。なんだこれは。少しだけ考えた。

問題

 問題が発生したのはTODOの内容をクリックして、既存のタスクを編集する箇所。今までのfunctionではOKだったが、アロー関数にするとなんだかおかしい。

コード

 問題点を明らかにするためにその箇所だけを抜き出して別にコードを書いた。「テスト」をクリックするとアロー関数のやつが実行されて、「テスト2」だと普通の関数が実行される。
 テストをクリックして、入力フォームのカーソルを外すと

 「Uncaught TypeError: Cannot read property 'classList' of null」

 というエラーが出てくる。
 

test.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8"/>
    <link rel ="stylesheet" href="todo.css">
    <title>イベントハンドラテスト</title>
</head>
<body>
  <header>
    <h1>TODOテスト</h1>
  </header>
  <div class="container">
    <div class="todo-container">
      <ul class="todo-list">
        <li>
          <span class="todo-content1">テスト</span>
        </li>
        <li>
          <span class="todo-content2">テスト2</span>
        </li>
      </ul>
    </div>
  </div>
    <script src = "test.js"></script>
</body>
</html> 
const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('on')) {
    itemToEdit.classList.add('on');
    let contentBeforeEdit = itemToEdit.textContent;
    itemToEdit.innerHTML = '<input type="text" class="editbox1" value="'+contentBeforeEdit+'" />';
    const editContent1 = document.querySelector('.editbox1');
    
    const saveTodoContent  = (e) => {
        let itemToSave = e.target;
        itemToSave.parentNode.classList.remove('on');
        let txtvalue = itemToSave.value;
        if (txtvalue ==''){
         txtvalue = itemToSave.defaultValue;
        }
        itemToSave.parentNode.innerHTML = txtvalue;
        
    }
    editContent1.addEventListener('blur',saveTodoContent);
  }
}

function editTodo2(e) {
  if(!this.classList.contains("on")) {
    this.classList.add("on");
    let contentBeforeEdit = this.textContent
    this.innerHTML = '<input type="text" class="editbox2" value="'+contentBeforeEdit+'" />'
    const editContent2 = document.querySelector(".editbox2")

    let saveTodoContent = function(){
      this.parentNode.classList.remove('on')
      let txtvalue = this.value
      if (txtvalue ==""){
       txtvalue = this.defaultValue
      }
      this.parentNode.innerHTML = txtvalue
    
    }
    editContent2.addEventListener("blur",saveTodoContent)
  }
}

//Select DOM
const todoContent = document.querySelector('.todo-content1');
todoContent.addEventListener('click', editTodo);
const todoContent2 = document.querySelector('.todo-content2');
todoContent2.addEventListener('click', editTodo2);

問題を追う

 カーソルを外した時に「classListがnullですよ」というエラーが発生する。ちょうどこの行である。

 const saveTodoContent  = (e) => {
//
      itemToSave.parentNode.classList.remove('on');
//

 Chromeのデベロッパーモードで少しずつ検証していった。そうするとカーソルを外す時に実行されるイベントsaveTodoContentが2回実行されていることがわかった。そして2回目の実行時にエラーが発生していた。なぜだ。

 だいたいはthisが原因だった。

原因

 この機能では項目の状態を知る必要がある。つまり「タスクが現在入力モードなのか否か」ということを知りたいのだ。コード上ではclasslistにonというclassを付与したり外したりしてそれを操作している。
 今まではその検知にthisを用いていたが、今回アロー関数に書き換えたことによってthisが使えなくなり、別の記法で書き換えた。そこがよくなかったのだ。

 ここである。

変更前
function editTodo2(e) {
  if(!this.classList.contains("on")) {
// .. クリックときに編集モードでなければ以下を実行
変更後
const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('on')) {
// .. クリックときに編集モードでなければ以下を実行

this/e.target

thisとe.targetはなにが違うのか。thisは場面によってさまざまに容態を変化させるが、ここでは以下の記事を参考にするならば「関数を呼び出している元のオブジェクト」である。

 e.targetとはクリックしたときの物体である。テキスト文字であったり、入力フォームであったりする。

 thisの中身はタスクが編集モードか否かでも変わらない。しかしe.targetは中身が変わる。ためしにconsole.logで読んでみよう。

const editTodo = (e) => {
  let itemToEdit = e.target;
  console.log(e.target);
  console.log(this);

//


function editTodo2(e) {
  console.log(e.target);
  console.log(this);

 
スクリーンショット 2021-03-29 5.25.11.png

上4行はアロー関数のほう。下はいままでの関数だ。たしかになんか違っていた。

どうしたか

 アロー関数にはイベントが実行される条件を増やした。

const editTodo = (e) => {
  let itemToEdit = e.target;
  if(!itemToEdit.classList.contains('todo-content1')){
    return;
  }
  if(!itemToEdit.classList.contains('on')) {
//....

 これで問題は解決した。

おわりに

 文章がまとまらないままこれを書き出してしまった。それでもどこかに残しておかないと、後々自分が困ってしまう。

 もしかしたら追記するかもしれないししないかもしれない。

 

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?