React 的 controller component 與 uncontrolled
此為 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 的值囉~