React 的 controller component 與 uncontrolled


Posted by 半夏 on 2021-08-17

此為 React 學習筆記,純粹用自己好記憶的方式所寫喔~

常見表單的控制方法,分為
controller component (有被React 控制著)
uncontrolled (沒有被 React 控制)


controller component

使用 value={value} onChange={handleInputClick} 做 controller

setTodos([value,...todos]) 這個方法會新增一個新的陣列,產生新的 todo。
這邊要注意的是不能用 push 對陣列做改變,因為 push 是改變原來的陣列
React 每次重新呼叫 function 會認為值沒有改變,所以畫面就不會變。

setValue(e.target.value) 會拿到輸入的值

App.js

import styled from 'styled-components';
import TodoItem from './TodoItem';
import { useState } from 'react';

function App() {
  const [todos, setTodos] = useState([
    1
  ])

  const [value, setValue] = useState('')

  const handleButtonClick = () => {
    setTodos([value,...todos]) // 產生新的 todo
    setValue('') 
  }

  const handleInputClick = (e) => {
    setValue(e.target.value)  // 拿到 input 輸入的值
  }

  return (
    <div className="App">
      <input type="text" value={value} onChange={handleInputClick} />
      <button onClick={handleButtonClick}>Add todo</button>
      {
        todos.map((todo, index) => <TodoItem key={index} content={todo} />)
      }
    </div>
  );
}

export default App;

uncontrolled

有二種方法,一種是常見的 ducument.querySelector 抓取 className 名稱
一種是用 useRef 的方式。

ducument.querySelector 抓取 className 名稱

function App() {
   const handleButtonClick = () => {
    document.querySelector('.input-todo').value()
  }

  return (
      ...
       <input className="input-todo" type="text"  />
      ...
  )
}

useRef 的方式
首先 import useRef 來用。
設定變數 const inputRef = useRef()
input 加上 ref={inputRef}

useRef 可以像 state 一樣操作,但是在重新 Render 的時候不會改變。

console.log(inputRef.current.value) 可以查看取到的值
在這裡 inputRef 是物件,物件裡面會有 current ,可以拿到所選的物件(<input type="text">),是 React 提供的一種方法。
不太懂的話可以自己 console.log 幾次就知道了


import { useState, useRef } from 'react';



function App() {
  const [todos, setTodos] = useState([
    1
  ])

  const [value, setValue] = useState('')
  const inputRef = useRef()

  const handleButtonClick = () => {
    console.log(inputRef.current.value) 
    setTodos([value,...todos])
    setValue('')
  }

  const handleInputClick = (e) => {
    setValue(e.target.value)
  }

  return (
    <div className="App">
      <input ref={inputRef} type="text" onChange={handleInputClick} />
      <button onClick={handleButtonClick}>Add todo</button>
      {
        todos.map((todo, index) => <TodoItem key={index} content={todo} />)
      }
    </div>
  );
}

上面的範例都是用 index 當作 key 的值,但是這樣寫比較不好,應該讓每個 todo 都有獨特的 id ,所以可以改成這樣:

App.js

import TodoItem from './TodoItem';
import { useState } from 'react';


let id = 2  // 因為 function 每次都會重新被呼叫,所以 id 要放在 function 外面
function App() {
  const [todos, setTodos] = useState([
    { id: 1, content: 'abc' }
  ])

  const [value, setValue] = useState('')

  const handleButtonClick = () => {
    setTodos([{  
      id,
      content: value
    },...todos])
    setValue('')
    id++   // 由 setTodos 操作 state 讓 id + 1
  }

  const handleInputClick = (e) => {
    setValue(e.target.value)
  }

  // key 的值改成 todo.id
  return (
    <div className="App">
      <input type="text" value={value} onChange={handleInputClick} />
      <button onClick={handleButtonClick}>Add todo</button>
      {  
        todos.map(todo => <TodoItem key={todo.id} todo={todo} />)
      }
    </div>
  );
}

export default App;

TodoItem.js
增加一個 data-todo-id={todo.id} 確認 id 是不是有正確


import './App.css';
import styled from 'styled-components';
import { ThemeProvider } from 'styled-components';
import {MEDIA_QUERY_M, MEDIA_QUERY_L} from './constants/breakpoint';

const theme = {
  colors: {
    primary_300: '#ff0000',
    primary_600: '#dd0000',
    primary_900: '#yy0000',
  }
}

const Title = styled.h1`
  font-size: 36px;

  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

const TodoContent = styled.div`
  color: ${props => props.theme.colors.primary_300};
  font-size: ${props => props.size === 'XL' ? '20px' : '16px'};
`

const BlackTodoItem = styled(TodoItem)`
  background: #000000;
`

const TodoItemWrapper = styled.div`
  padding: 20px;
  border: solid 1px #000000;
  display: flex;
  align-items: center;
  justify-content: space-between;

  ${MEDIA_QUERY_M} {
    border: solid 2px red;
  }
`

const Button = styled.button`
  padding: 4px;
  background-color: blue;
  color: #ffffff;
`

const ReButton = styled(Button)`
  background-color: #ff0000;
`

function Counter() {
  alert(1)
}

function TodoItem({className, size, todo, title }) {
  return (
    <ThemeProvider theme={theme}>
      <Title>{title}</Title>
      <TodoItemWrapper className={className} data-todo-id={todo.id}>
        <TodoContent size={size}>{todo.content}</TodoContent>
        <div>
          <Button>已完成</Button>
          <ReButton>刪除</ReButton>        
        </div>
      </TodoItemWrapper>
    </ThemeProvider>
  );
}

export default TodoItem;

這樣就有 id 的值囉~


#React #controller #uncontrolled







Related Posts

畫frequency response和phase response(MATLAB))

畫frequency response和phase response(MATLAB))

HTB Blackfiled Walkthrough

HTB Blackfiled Walkthrough

What is the concept of handle in Java?

What is the concept of handle in Java?


Comments