Hooks 是React16.8最新引入的特性。使用Hooks可以在函数式组件中管理state。
Hooks 带来的好处可以让更简洁的让UI与状态分离,使代码更加清晰。
说明
使用Hooks的前提是在函数式组件中。所以不能再使用React类组件的几个生命周期函数(需要通过useEffect
来实现)
开始
前端项目万物基于TODO APP 😂 , 接一下使用Hooks来创建一个TODO APP。
useState
useState 接受一个初始化参数, 返回一个值和set方法
todo 组件:
const todo = props => {
const [todoName, setTodoName] = useState('')
const [todoList, setTodoList] = useState([])
const inputChangeHandler = event => {
setTodoName(event.target.value)
}
const todoAddHandler = () => {
setTodoList(todoList.concat(todoName))
}
return (
<React.Fragment>
<input type="text" placeholder="Todo"
value={todoName}
onChange={inputChangeHandler} />
<button type="button" onClick={todoAddHandler}>Add</button>
<ul>
{todoList.map((item, index) => (
<li key={index}>item</li>
))}
</ul>
</React.Fragment>
)
}
useEffect
通过向useEffect穿不同的参数,可以模拟原来类组件中的生命周期方法。
const mouseMoveHandler = event => {
console.log(event.clientX, event.clientY)
}
useEffect(() => {
document.addEventListener('mousemove', mouseMoveHandler)
return () => {
document.removeEventListener('mousemove', mouseMoveHandler)
}
}, [])
使用useEffect(fn, [])
可以差不多的模拟componentDidMount
。 不传第二个deps参数, 每次render都会执行useEffect
useEffect
返回callback 表示 clean up,模拟 unmount
。在[]
可以中指定监听的对象scope ,当对象改变时,执行useEffect
。
这篇文章详细的说明了useEffect中异步state状态更新和this指向的问题
useContext
在类组件中使用React context,需要使用 jsx
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
这样包起来,然后有了useContext, 在子组件直接使用即可
const context = useContext(MyContext)
useRef
useRef返回一个可变的ref引用。可以把TODO input输入改写为:
const todo = props => {
const todoInputRef = useRef()
...
//获取input的值
// const todoName = todoInputRef.current.value
return (
<React.Fragment>
<input type="text" placeholder="Todo"
ref={todoInputRef}
...
</React.Fragment>
)
}
useReducer
在API处理异步的时候当网络慢的时候state会更新错误,比如:
const todoAddHandler = () => {
axios.post('api', {name: todoName})
.then(res => {
setTimeout(() => {
const todoItem = {id: res.data.name, name: todoName }
//当连续添加时,当前的todoList只能更新一个值
setTodoList(todoList.concat(todoItem))
}, 3000)
}).catch(err => {
console.log(err)
})
}
可以添加一个flagState来处理
const [submittedTodo, setSubmittedTodo] = useState(null)
useEffect(() => {
if (submittedTodo) {
setTodoList(todoList.concat(submittedTodo))
}
}, [submittedTodo])
const todoAddHandler = () => {
axios.post('api', {name: todoName})
.then(res => {
setTimeout(() => {
const todoItem = {id: res.data.name, name: todoName }
// 只更新submittedTodo
setSubmittedTodo(todoItem)
}, 3000)
}).catch(err => {
console.log(err)
})
}
当然 更好的方法可以使用useReducer来解决这个问题
const todoListReducer = (state, action) => {
switch (action.type) {
case 'ADD':
return state.concat(action.payload)
case 'SET':
return action.payload
case 'REMOVE':
return state.filter(todo => todo.id !== action.payload)
default:
return state
}
}
const [todoList, dispatch] = useReducer(todoListReducer, [])
const todoAddHandler = () => {
axios.post('api', {name: todoName})
.then(res => {
setTimeout(() => {
const todoItem = {id: res.data.name, name: todoName }
dispatch({type: 'ADD', payload: todoItem})
}, 3000)
}).catch(err => {
console.log(err)
})
}
useMemo
useMemo 用于优化性能,省略不必要的重新render
比如 将 TODO App 每一个item 封装成一个List组件:
// List
const list = props => {
console.log('Rendering the list...')
return <ul>
{
props.items.map(todo => (
<li key={todo.id} onClick={props.onClick.bind(this,todo.id)}>{todo.name}</li>
))
}
</ul>
}
// TODO
const todo = props => {
...
return (
....
{useMemo(() =>
<List items={todoList} onClick={todoRemoveHandler}/>, [todoList]
)}
)
}
自定义hook
自定义一个useFormInput
// form.js
export const useFormInput = () => {
const [value, setValue] = useState('')
const [validity, setValidity] = useState(false)
const inputChangeHandler = event => {
setValue(event.target.value)
if(event.target.value.trim() === '') {
setValidity(false)
} else {
setValidity(true)
}
}
return {
value: value,
onChange: inputChangeHandler,
validity
}
}
//todo.js
const todo = props => {
const todoInput = useFormInput()
return (
...
<React.Fragment>
<input type="text" placeholder="Todo"
onChange={todoInput.onChange}
value={todoInput.value}
style={{backgroudColor: todoInput.validity ? 'transparent' : 'red'}}
/>
...
)
}