介绍
React 是一个用于构建用户界面的 JavaScript 库。
使用 React 可以创建复杂的 UI,同时使代码易于理解。
React:UI = render(data) 单向数据流
vue:ref 能够直接操作dom,并非是 view model
react:单向数据流,不能通过双向绑定,实现响应式
JSX
JSX: JavaScript XML,将 UI 与逻辑层耦合在一起,提高代码的可读性和可维护性,在组件中使用JSX语法,可以直接在 JS 代码中编写 HTML 结构,而不需要用 JS 字符串拼接。
1 2
| <div class="" table-index> <div className="" tableIndex>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const name = 'react' const element = <h1>Hello, {name}</h1>
function formatName(user) { return user.firstName + ' ' + user.lastName }
const user = { firstName: 'Harper', lastName: 'Perez' }
const element = ( <h1> Hello, {formatName(user)}! </h1> )
function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1> } return <h1>Hello, Stranger.</h1> }
const element2 = getGreeting(user)
|
1 2
| const element = <div tabIndex="0"></div> const element = <img src={user.avatarUrl}></img>
|
React 式预防 XSS 攻击,会对所有的输入进行转义,所以不会导致 XSS 攻击。
XSS 攻击:跨站脚本攻击,是一种代码注入攻击,攻击者在网页中注入恶意脚本,当用户浏览网页时,脚本会被执行,从而达到攻击目的。
1 2 3
| <textarea> <img src="error.png" onError="alert('XSS')"/> </textarea>
|
其实就是使用转义字符,将特殊字符转义为HTML实体,React中使用{}来转义。
1 2 3 4 5 6 7 8 9 10
|
for (let i = 0; i < comments.length; i++) { comments[i] = '<li>' + comments[i] + '</li>' } return ( <ul> {comments} </ul> )
|
1 2 3 4 5 6 7 8 9 10 11
| const element = ( <h1 className="greet"> Hello, world! </h1> )
const element = React.createElement( 'h1', {className: 'greet'}, 'Hello, world!' )
|
1 2 3 4 5
| const element = <h1>Hello, world</h1>
const container = document.getElementById('root')
ReactDOM.render(element, container)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div id="root"> <h1>Hello, world</h1> </div>
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ) ReactDOM.render(element, document.getElementById('root')) }
setInterval(tick, 1000)
|
props 和 state
组件
组件是 React 应用的基本构建块,通过组件可以将 UI 拆分成独立的、可复用的部分。
1 2 3 4 5 6 7 8 9 10 11
| function Welcome(props) { return <h1>Hello, {props.name}</h1> }
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1> } }
|
组件组合以及拆分
组件可以在其输出中引用其他组件,这使得我们可以使用相同组件抽象出任意层次的细节。
1 2 3 4 5
| <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function Welcome(props) { return <h1>Hello, {props.name}</h1> }
function App() { return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ) }
ReactDOM.render( <App />, document.getElementById('root') )
|
这段代码会渲染出一个包含三个 Welcome 组件的 App 组件。通过这个例子,我们可以看到组件在输出中可以引用其他组件,这让我们可以用同一组件来抽象出任意层次的细节。按钮、表单、对话框、整个屏幕的内容:在 React 应用中,所有这些通常都会以组件的形式表示。
拆分组件是 React 应用中最常见的模式。这里有一个关于 App 组件如何拆分成更小的组件的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function formatDate(date) { return date.toLocaleDateString() }
function Comment(props) { return ( <div className="Comment"> <div className="UserInfo"> <img className="Avatar" src={props.author.avatarUrl} alt={props.author.name} /> <div className="UserInfo-name"> {props.author.name} </div> </div> <div className="Comment-text"> {props.text} </div> <div className="Comment-date"> {formatDate(props.date)} </div> </div> ) }
const comment = { date: new Date(), text: 'I hope you enjoy learning React!', }
const author = { name: 'Hello Kitty', avatarUrl: 'https://google.com/favicon.ico', }
ReactDOM.render( <Comment date={comment.date} text={comment.text} author={author} />, document.getElementById('root') )
|
这个示例演示了如何将一个更复杂的组件拆分成更小的组件。Comment 组件描述了一个评论,包括作者的名字、头像和评论内容。这个组件在其结构中使用了 Avatar、UserInfo 和 UserInfoName 组件,将其拆分成更小的组件有助于理清应用的 UI 架构。
受控组件以及非受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class NameForm extends React.Component { constructor(props) { super(props) this.state = {value: ''}
this.handleChange = this.handleChange.bind(this) this.handleSubmit = this.handleSubmit.bind(this) }
handleChange(event) { this.setState({value: event.target.value}) }
handleSubmit(event) { alert('A name was submitted: ' + this.state.value) event.preventDefault() }
render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ) } }
ReactDOM.render( <NameForm />, document.getElementById('root') )
|
这个示例展示了一个受控组件,输入框的值由 React 控制。每次按键时,handleChange 事件处理程序都会被调用,更新 React 的 state,因此输入框的值始终与 state 中的值保持一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class NameForm extends React.Component { constructor(props) { super(props) this.handleSubmit = this.handleSubmit.bind(this) this.input = React.createRef() }
handleSubmit(event) { alert('A name was submitted: ' + this.input.current.value) event.preventDefault() }
render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={this.input} /> </label> <input type="submit" value="Submit" /> </form> ) } }
ReactDOM.render( <NameForm />, document.getElementById('root') )
|
这个示例展示了一个非受控组件,输入框的值由 DOM 控制。每次按键时,handleSubmit 事件处理程序都会被调用,从输入框中获取值。
props 和 state 的区别
props 和 state 的使用场景
props 和 state 示例
props 示例:
1 2 3 4 5 6 7 8 9 10
| class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1> } }
ReactDOM.render( <Welcome name="Sara" />, document.getElementById('root') )
|
state 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Clock extends React.Component { constructor(props) { super(props) this.state = {date: new Date()} }
render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ) } }
ReactDOM.render( <Clock />, document.getElementById('root') )
|
这两个示例展示了 props 和 state 的使用场景。Welcome 组件通过 props 接收父组件传递的数据,Clock 组件通过 state 维护内部数据。区别在于:props 是父组件传递给子组件的数据,子组件不能修改 props;state 是组件内部维护的数据,组件内部可以修改 state。
生命周期
React 组件有三个生命周期阶段:
1.render
class 组件中必须有 render 方法,render 方法是 class 组件中唯一必须的方法。
获取最新的 state 和 props,返回一个 React 元素。
在不想修改 state 或 props 的情况下,不要修改 render 方法中的数据。
2.constructor
构造函数,用于初始化 state 和绑定事件处理方法。
声明 state,初始化 state。
绑定事件处理方法。
避免将 props 复制到 state 中,因为 props 是只读的,state 可以修改。
1 2 3 4 5
| constructor(props) { super(props) this.state = {count: 0}; this.handleClick = this.handleClick.bind(this); }
|
不要用 setState 方法来修改 state,因为 setState 方法是异步的,可能会导致 state 不是最新的。
1 2
| this.state = { color: props.color } this.setState({ color: props.color })
|
3.componentDidMount
组件挂载后调用,只调用一次。
setState 方法会触发重新渲染
addEventListener 添加事件监听
4.componentDidUpdate
组件更新后调用,除了首次渲染,其他渲染都会调用。
1 2 3 4 5 6 7 8 9 10
| componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate(prevProps) { if (prevProps.color !== this.props.color) { this.setState({ color: this.props.color }) } }
|
5.componentWillUnmount
组件卸载前调用,用于清理工作。
6.shouldComponentUpdate
这个方法用于判断是否需要重新渲染组件,默认返回 true。并不是很常用,因为 React 会自动判断是否需要重新渲染组件。
1 2 3 4 5 6 7 8 9
| shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true } if (this.state.count !== nextState.count) { return true } return false }
|
7.getDerivedStateFromProps
用于取代 componentWillReceiveProps,每次组件接收新的 props 时都会调用。
但是不推荐使用,因为会导致组件状态不可预测。
根据新的 props 更新 state,返回一个对象,或者返回 null 表示不更新 state。
1 2 3 4 5 6
| static getDerivedStateFromProps(props, state) { if (props.color !== state.color) { return { color: props.color } } return null }
|
8.getSnapshotBeforeUpdate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| getSnapshotBeforeUpdate(prevProps, prevState)
class ScrollingList extends React.Component { listRef = React.createRef()
getSnapshotBeforeUpdate(prevProps, prevState) { if (prevProps.list.length < this.props.list.length) { const list = this.listRef.current return list.scrollHeight - list.scrollTop } return null }
componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot !== null) { const list = this.listRef.current list.scrollTop = list.scrollHeight - snapshot } }
render() { return <div ref={this.listRef}>{/* ...contents... */}</div> } }
|
9.static getDerivedStateFromError
错误边界 error boundary,用于捕获子组件的错误,只能捕获子组件的错误,无法捕获自身的错误。
1 2 3 4
| static getDerivedStateFromError(error) { return { hasError: true } }
|
10.componentDidCatch
1 2 3 4
| componentDidCatch(error, info) { logErrorToMyService(error, info) }
|