基础
组件
React组件包括class组件和函数组件。
当组件内部state
改变或props
地址改变的时候组件会重新渲染,并且对于非纯组件来说,父组件的渲染也会引起子组件的渲染。
我们可以使用React.PureComponent
或React.memo
实现纯组件。
class Akara extends React.PureComponent { // 纯类组件
// ...
}
const App = React.memo(() => <div>akara</div>) // 纯函数组件
状态
在类组件中通过this.setState
修改数据。
this.setState({name: 'aka'})
this.state = Object.assign(this.state, { name: 'aka' }) // 简单地理解
通常状态的更新是异步的。
this.setState({
count: 1 // 假设原始值为0
})
console.log(this.state.count) // 输出0而不是1
想要获取修改后的值,我们可以传一个回调函数给setState
this.setState({
count: 1
}, () => {
console.log(this.state.count) // 输出1
})
如果setState
依赖之前的State,setState
的参数可以为函数
this.setState((state, props) => {
return {
count: state.count + 1
}
})
事实上,在合成事件和组件的生命周期中setState
是异步的;而在原生事件和定时器中setState
是同步的。
这是因为,React内部维护了一个标识:isBatchingUpdates
。在合成事件和组件的生命周期中,该值为true
,那么setState
会被缓存进队列,最后才批量更新;而在原生事件和定时器中,该值为false
,调用setState
时会直接同步更新。
事件
React中的event
是合成事件,我们无需担心任何浏览器的兼容性问题。
<button onClick={this.handleClick}>
aka
</button>
我们有几种方式绑定事件。
// 方法一
constructor() {
this.handlerClick = this.handlerClick.bind(this)
}
// 方法二
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
// 方法三
class Btn extends React.component {
handlerClick = (e) => {
console.log(this)
}
render() {
return (
<button onClick={this.handlerClick}>
Click me
</button>
)
}
}
生命周期
详细的生命周期可以参考该链接
Form
React把表单元素分为受控组件和非受控组件,默认的input
元素被视为非受控组件,此时我们通常会给defaultValue
属性;当我们给input
元素加上value
和onChange
属性时该元素被视为受控组件,此时input
元素的内部值完全由我们的状态控制。
// 受控组件
handleChange = (e) => {
this.setState({
name: e.target.value.toUpperCase()
})
}
render() {
return <input type="text" value={name} onChange={this.handleChange}/>
}
Ref and forwardRef
使用现代框架的一大特点是基于数据驱动的页面渲染,避免了复杂的DOM操作。有的时候我们就是想使用DOM,此时可以使用ref
。
class App extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
handleClick = () => {
this.myRef.current.focus()
}
render() {
return (
<div>
<input type="text" ref={this.myRef} />
<button onClick={this.handleClick}>点我</button>
</div>
)
}
}
对于一个FancyButton
组件,我们或许希望能用ref
拿到组件内部的元素,此时可以使用Refs转发。
const FancyButton = React.forwardRef((props, ref) => {
return <button ref={ref}>aka</button>
})
<FancyButton ref={myRef}></FancyButton>
Context
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
类组件
const ThemeContext = React.createContext("akara");
export const MyProvider = ThemeContext.Provider;
const App = () => {
return (
<div>
<Test />
</div>
);
};
class Test extends React.Component {
static contextType = ThemeContext
render() {
return <div>{this.context}</div>
}
}
函数式组件
const ThemeContext = React.createContext("akara");
export const MyProvider = ThemeContext.Provider;
export function useMyContext() {
const context = useContext(ThemeContext);
return context;
}
const App = () => {
return (
<div>
<MyProvider value="bkara">
<Test />
</MyProvider>
</div>
);
};
const Test = () => {
const context = useMyContext();
return <div>{context}</div>;
};
Fragment
function App() {
return (
<>
<React.Fragment>
<div></div>
<div></div>
</React.Fragment>
</>
)
}
Hight-Order Components
高阶组件是参数为组件,返回值为新组件的函数。
// 官网例子
function App () {
const MouseWithCat = withMouse(Cat)
return (
<MouseWithCat />
)
}
function Cat (props) {
let {x, y} = props.mouse
x += 20
y += 20
return (
<img src='https://messiahhh.github.io/blog/logo.png' style={{position: 'absolute', left: x , top: y, width: '40px', height: '40px'}}/>
)
}
function withMouse(WrappedComponent) {
return function () {
let [point, setPoint] = useState({
x: 0,
y: 0,
})
const move = (e) => {
setPoint({
x: e.clientX,
y: e.clientY
})
}
return (
<div style={{height: '100vh'}} onMouseMove={move}>
鼠标的位置:{ point.x } , { point.y }
<WrappedComponent mouse={point} />
</div>
)
}
}
Render Props
// 官网例子
function App () {
return (
<Mouse render={point =>
<Cat mouse={point} />
}/>
)
}
function Cat (props) {
let {x, y} = props.mouse
x += 20
y += 20
return (
<img src='https://messiahhh.github.io/blog/logo.png' style={{position: 'absolute', left: x , top: y, width: '40px', height: '40px'}}/>
)
}
function Mouse(props) {
let [point, setPoint] = useState({
x: 0,
y: 0,
})
const move = (e) => {
setPoint({
x: e.clientX,
y: e.clientY
})
}
return (
<div style={{height: '100vh'}} onMouseMove={move}>
鼠标的位置:{ point.x } , { point.y }
{props.render(point)}
</div>
)
}
Error Boundary
当某个组件抛出异常时,开发模式下能在页面看到错误信息,而生产模式下整个页面都白屏了。在生产模式下为了避免某个组件的错误让整个应用都塌陷,我们可以使用Error Boundary
组件来捕捉和打印组件内部的异常信息,并提供页面的降级处理。
一个类组件被视为错误边界组件的前提是我们给该组件定义了static getDerivedStateFromError()
或componentDidCatch()
,通常我们会同时提供这两个方法。static getDerivedStateFromError()
的作用是当出现异常时提供降级UI,componentDidCatch()
内部通常用来打印错误日志。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
hasError: false
}
}
static getDerivedStateFromError(error) {
return {
hasError: true
}
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>i'm fallback UI</div>
}
return this.props.children
}
}
const App = () => {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
setCount(count + 1)
}, [count])
if (count >= 5) {
throw new Error('i crashed!')
}
return (
<>
{count}
<div onClick={handleClick}>点击</div>
</>
);
};
ReactDOM.render(
<ErrorBoundary>
<App />
</ErrorBoundary>,
document.querySelector("#root")
);
Portals
const App = () => {
return (
<>
<div>hello akara</div>
<Modal>
<div>i'm modal</div>
</Modal>
</>
);
};
const Modal = (props) => {
return ReactDOM.createPortal(
props.children,
document.querySelector('body')
)
}