Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

React ref解説

refとは?

refとはReference(参照)のことです。ミュータブルな値を管理するときに使います。

refの使いみちの最たるものはDOMへのアクセスです。たとえばinput要素が持つfocusメソッドを呼びたいときにrefを使います。

const App = () => {
    const ref = useRef();
    useEffect(() => ref.current.focus(), []);
    // レンダー後、inputにフォーカスする
    return <input ref={ref}/>;
};

上記の例では、refはDOMへの参照となっています。もちろん、refを使えばinputのvalueを変更したり、focusする以外にも様々なことを命令的にできます。しかし、DOMを命令的に操作するのはReactのやり方に相反します。そのため、できる限りrefを使わず宣言的に書く方法を考えるべきです。

refは単にミュータブルなオブジェクトです。そのため、生DOMへのアクセス以外にも使うことができます。ただし、そこまで用途はありません。というより、他の手法で十分なことがほとんどです。refを使いたくなったら一旦落ち着いて他の方法を考えましょう。

forwardRef

通常、関数コンポーネントはrefを受け取ることができません。たとえば下のMyInputのようなことはできません。

// ダメな例(関数コンポーネントはrefを受け取れない)
const MyInput = ({ref}) => (
  <input ref={ref}/>
);
const App = () => {
  const ref = useRef();
  const onClick = () => { ref.current.focus() };
  // MyInputにrefは渡せない!
  return (
    <div>
      <MyInput ref={ref}/>
      <button onClick={onClick}>focus</button>
    </div>
  );
}

refは特殊なpropなので、そのままでは受け取れません。forwardRefを使ってやると関数コンポーネントでもrefを受け取れます。

// forwardRefを使えば関数コンポーネントでもrefを受け取れる
const MyInput = React.forwardRef((prop, ref) => (
  <input ref={ref}/>
));

String refを使っていたコンポーネントをhooksで書き直す

useImperativeHandleを使うと、string refを使っていたクラスコンポーネントを関数コンポーネントへ書き直すことができます。

以下のChildコンポーネントはstring refを使って複数のinputへのrefを親コンポーネントへ提供しています。

// string refはclass componentでしか扱えない
class Child extends React.Component {
  render() {
    return (
      <form>
        <input type="email" ref="email"/>
        <input type="password" ref="password"/>
        <input type="text" ref="username"/>
      </form>
    );
  }
}
class App extends React.Component {
  focusOnEmail() {
    // Child内のinput[type=email]へアクセスできる
    this.refs.form.refs.email.focus();
  }
  render() {
    return (
      <div>
        <Child ref="form"/>
        <button onClick={this.focusOnEmail.bind(this)}>focus on email</button>
      </div>
    )
  }
}

Childコンポーネントをhooksを用いて関数コンポーネントへ書き直します。

const Child = React.forwardRef((prop, ref) => {
  const emailRef = useRef(),
    passwordRef = useRef(),
    usernameRef = useRef();
  useImperativeHandle(ref, () => {
    return {
      refs: {
        get email() { return emailRef.current; },
        get password() { return passwordRef.current; },
        get username() { return usernameRef.current; },
      }
    };
  })
  return (
    <form>
      <input type="email" ref={emailRef}/>
      <input type="password" ref={passwordRef}/>
      <input type="text" ref={usernameRef}/>
    </form>
  );
});

これで従来のstring refでできたことが再現できます。

(型をつけるのが非常に困難なので、こんな方法するくらいなら、素直にリファクタリングしましょう)

そもそも

ただし、そもそもこれはstring refからhooksへ置き換えられるだけです。古いクラスコンポーネントを書き直すとき以外、こんな方法しなくて十分です。というのも幾つか理由があります。

  • Appコンポーネント側からrefs以下に何があるか分からない
  • 下のコンポーネントでhookを使いたくない

そのため、個人的にこの方法は好きではありません。

いちから作るのであれば、まずApp側でrefを作り、Childはそれを受け取って紐付けるだけにするとシンプルになります。以下はChildをSFCにしてrefを受け取る例です。

const Child = ({ emailRef, passwordRef, usernameRef }) => (
  // useImperativeHandleのようなことをする必要がない!
  <form>
    <input type="email" ref={emailRef} />
    <input type="password" ref={passwordRef} />
    <input type="text" ref={usernameRef} />
  </form>
);

const App = () => {
  const emailRef = useRef(),
    passwordRef = useRef(),
    usernameRef = useRef();
  // どういうrefがあるのかApp側が明示的に持てる
  const focusOnEmail = () => emailRef.current.focus();
  return (
    <div>
      <Child
        emailRef={emailRef}
        passwordRef={passwordRef}
        usernameRef={usernameRef}
      />
      <button onClick={focusOnEmail}>focus on email</button>
    </div>
  );
};

TypeScriptでの型付け的にもこちらのほうが筋が通っています。

もちろん、下のほうの実装を隠蔽したいときはuseImperativeHandleが活躍します。

イトーキF レビュー

中古でイトーキのFというオフィスチェアを購入したのでレビューです。

感想

すごくいいです。座椅子でずっと作業してたのですが、苦労が全てなくなりました。

ヘッドレストがないモデルですが、あまり気になりません。ディスプレイを買ったので下を向いて作業することがなくなったのも、おそらくはヘッドレストが要らない要因の一つです。

肘掛けは絶対にあったほうがいいと思いました。肘掛けあるとすごく楽に作業できます。試しに肘掛けを外に向けて肘が宙にある状態で作業してみましたが、圧倒的にやりづらいです。可動式の肘掛け、これは本当にオススメです。写真ではあまり内側への角度調整ができなそうに見えましたが、十分でした。めっちゃいいです。

座り心地もいいですね。「わー高級!」みたいにはなりませんが、疲れません。若干背もたれとの間に隙間があるのが気になりますが、疲れないので問題ないです。

結構背が高い(180cm弱)ですが、十分ゆったりと使えます。いい椅子です。

HHKB所感

HHKB HYBRID Type-Sを買ったのでレビューします。

所感

  • ノートのキーボードより打ちやすい
  • Bluetooth接続でも遅延をほとんど感じない
  • Fnキーは気にならないが、チルダキーの位置がとんでもないのでつらい
  • Ctrl + Shift + Spaceで入力切り替えというのはやってみると案外手間に感じない
  • 音はType-Sという割にはスコスコなりまくっている。これでType-Sなら無印はどうなんだ…?と思っている
  • ホームポジションがでたらめな我流なのでHHKBのサイズ感だと打ち間違えが多い

ノートのキーボードより打ちやすい

これはまあ当然なのですが(35,000円もするので)、やはり打ちやすいですね。底につかないので指が疲れにくいです。

ぼくは研究室で1年間Realforceを使っていましたが、打鍵感はそんなに変わらない気がします。

Bluetooth接続でも遅延がほぼない

無線接続というとラグで使い物にならない印象がありますが、HHKBはほぼラグがありません。無線と有線で差が全くないです。タブレットやスマホと接続してもほとんどラグがなかったので、ほとんどの環境でラグがないと思われます。タブレットと無線で繋いでブログを書くという芸当ができるのはすごく楽でいいですね。

キー配列が地味に辛い

HHKBといえばFnキーを使った移動ですが、これはすぐ慣れるので全く問題ありません。それよりもチルダキーの位置が右上にあるのが非常に気になります(US配列の場合チルダキーは左上が多い)。チルダキーは結構使うので、真反対の位置に置かれると脳が混乱します。

また、僕はjキーに右手人差し指を置いてタイピングをする癖があります。HHKBはキーが小さいため、この我流ホームポジションだと感覚が狂います。左手はともかく、右手のタイプミスがかなり目立っています。まあ、ホームポジションが我流なのが問題なので、きちんとホームポジションができていればおそらく問題ありません。

日本語切り替えはそこまで手間でない

US配列には半角全角キー、無変換・変換キーがありません。そのため、日本語入力はCtrl + Space(あるいはCtrl + Shift + Space)で切り替える必要があります。僕は普段、無変換キーと変換キーで入力切替できるよう設定しているため、入力を切り替えるときに複数キーを押すのは面倒なんじゃないか?と思っていましたが、慣れるとそこまで手間に感じません。

Type-S…なのか?

Type-SでないHHKBを使ったことがないので比較ができませんが、Type-Sといいつつ結構カチャカチャと音がなっています。別に不満というわけではないですが、「これでType-Sってことは無印はどれだけ音が大きいんだ…」と思いました。もし無印を買っていたら音が気になっていたかもしれません。下手に金をケチるよりType-Sを買ったほうが良いのかもしれません。

まとめ

まとめると、「慣れればかなり快適」ですね。35,000円かける価値はあると思います。