1.目标
- 入门React,了解常规的用法
- 掌握面试中React的基础问题
- 掌握React学习路线
2.知识要点
初始化一个react app
- 确保你安装了较新版本的 Node.js。
- 按照 Create React App 安装指南创建一个新的项目
npx create-react-app my-app
2.1 React简介
React 是⼀个声明式,⾼效且灵活的⽤于构建⽤户界⾯的 JavaScript 库
2.2 JSX模板语法
JSX称为JS的语法扩展,ui与logic层的结合,使用{}标识
使用小驼峰命名
- class -> className
- table->tableIndex
用法
//变量
const name ='lance'
const element = <h1>{name}</h1>;
//调用方法
function getName(){
return 'lance'
}
const element = (<h1>{getName()}</h1>)
//表达式
const getGreeting = function(){
return <h1>{getName()}</h1>
}
//指定属性
const user = {
name:'lance'
}
const elememt =<img src={user.name}>
//表示对象
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 等同于React.createElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
//更新元素 也使⽤ ReactDOM.render,react只更新实际改变的内容
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}
JSX转JS
通过babel来转换JSX
可以在babel官⽹中尝试,babeljs.io/repl 可以使⽤官⽹提供的create-react-app npm run eject 来看babelrc中的配置,www.babeljs.cn/docs/babel-…
//安装babel及react 的依赖
npm install core-js @babel/core @babel/preset-env @babel/preset-react @babel/register babel-loader @babel/plugin-transform-runtime --save-dev
.babelrc 文件
{
"presets":[
"@babel/preset-env",
"@babel/preset-react"
],
"plugins":[
"@babel/plugin-transform-runtime"
]
}
2.3 props, state
props用于父组件向子组件传值不可被修改,state是当前组件的数据状态,可以出发组件重新渲染
- 函数式组件
- Class类组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
constructor(props){
super(props);
this.state={message:'hello'};
//这一行很重要
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
console.log(this.state.message)
}
render() {
<button onClick={this.handleClick}>say hello</button>
}
}
2.4 渲染组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
// ⾃定义组件使⽤⼤写字⺟开头
import React from 'react';
// 正确!组件需要以⼤写字⺟开头:
function Hello(props) {
// 正确! 这种 <div> 的使⽤是合法的,因为 div 是⼀个有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正确!React 知道 <Hello /> 是⼀个组件,因为它是⼤写字⺟开头的:
return <Hello toWhat="World" />;
}
2.5 调用生命周期钩子
class Welcome extends React.Component {
constructor(props){
super(props);
this.state={message:'hello'};
//这一行很重要
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
console.log(this.state.message)
}
componentDidMount(){
this.setState({
message:'hello'
})
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount(){
clearInterval(this.timerID);
}
render() {
<button onClick={this.handleClick}>say hello</button>
}
}
2.6 state
- 构造函数是唯⼀可以给state赋值的地⽅
- state更新可能是‘异步’的,异步⽬的:batch 处理,性能优化
- 单向数据流,只在当前的组件⾥⽣效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是 不⼀样的
- setState 只在合成事件和钩⼦函数中是“异步”的,在原⽣事件和 setTimeout 中都是 同步的;
- setState的“异步”并不是说内部由异步代码实现,其实本身执⾏的过程和代码都是同步 的, 只是合成事件和钩⼦函数的调⽤顺序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到 更新后的值,形式了所谓的“异步”,当然可以通过第⼆个参数 setState(partialState, callback) 中的callback拿到更 新后的结果。
- setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事 件和setTimeout 中不会批量更新,在“异步”中如果对同⼀个值进⾏多次 setState , setState 的批量更新策略会对其进⾏覆盖,取最后⼀次的执⾏,如果是同时 setState 多个不 同的值,在更新时会对其进⾏合并批量更新。
class Welcome extends React.Component {
constructor(props){
super(props);
this.state={message:'hello',counter:1};
//这一行很重要
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
}
componentDidMount(){
this.setState({
message:'hello'
})
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount(){
clearInterval(this.timerID);
}
render() {
<button onClick={this.handleClick}>say hello</button>
}
}
// setState 异步
// 异步⽬的:batch 处理,性能优化
1. 合成事件
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
2. ⽣命周期
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
3. 原⽣事件
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
4. setTimeout
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
5. 批处理
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
3. 生命周期
3.1.1 componentDidMount
组件挂载后,调用
import * as React from 'react';
class Lifehooks extends React.Component {
constructor (props) {
super();
this.state = {name: 'lance'};
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
alert('hi react');
}
componentDidMount () {
console.log('hi react');
}
render () {
return (
<div>
<h1>
hi {this.state.name}
</h1>
<button onClick={this.handleClick}>click me</button>
</div>
)
}
}
export default Lifehooks;
3.1.2 componentDidUpdate
更新后会被立即调用,首次渲染不会执行此方法
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate(prevProps) {
// 典型⽤法(不要忘记⽐较 props):加条件判断,不然死循环
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
如果组件实现了 getSnapshotBeforeUpdate() ⽣命周期,
则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此
参数将为 undefined。
3.1.3 componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调⽤。例如,清除 timer,取消⽹络请求; componentWillUnmount() 中不应调⽤ setState(),因为该组件将永远不会重新渲染;
3.1.4 shouldComponentUpdate(nextProps,nextState)
通过判断nextProps和nextState的值,返回false,可以控制render(), componentDidUpdate()不调用,
import * as React from 'react';
import {SearchBar} from './SearchBar';
import {ProductTable} from './ProductTable';
import {DATA} from './data';
import _ from 'lodash';
export class FilterableProductTable extends React.Component {
constructor (props) {
super(props);
this.handleOnSearchChange = this.handleOnSearchChange.bind(this);
this.data = FilterableProductTable.buildData();
this.state = {
showData: this.data,
};
}
static buildData () {
const data = DATA;
_.forEach(data, item => {
item['searchKey'] = `${item.name}${item.price}`;
});
return data;
}
handleOnSearchChange (value) {
const filterData = _.filter(this.data, item => {
return item.searchKey.includes(value);
});
this.setState(state => ({
showData: filterData,
}));
}
render () {
return (
<div>
<SearchBar onSearchChange={this.handleOnSearchChange}></SearchBar>
<ProductTable products={this.state.showData}></ProductTable>
</div>
);
}
}
3.1.5 getDerivedStateFromProps(nextProps,preState)
根据props的变化改变state
- 在使用此生命周期时,要注意nextProps和preState进行比较
- 静态方法,保持它是纯函数,不要产生副作用
3.1.6 getSnapshotBeforeUpdate(prevProps,prevState)
getSnapshotBeforeUpdate() 在最近⼀次渲染输出(提交到 DOM 节点)之前调⽤; 此⽣命周期⽅法的任何返回值将作为参数传递给 componentDidUpdate()。
import * as React from 'react';
export class ScrollList extends React.Component {
constructor (props) {
super(props);
this.state = {
list: this.props.list,
};
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate (prevProps, prevState) {
//比较list是否有更新
/// 我们是否在 list 中添加新的 items ?
// // 捕获滚动••位置以便我们稍后调整滚动位置。
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) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
//(这⾥的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render () {
return (
<div ref={this.listRef} style={{
height: '300px',
overflowY: 'auto',
}}>
{
this.state.list.map(item => {
return <div style={{height: '100px'}} key={item + 1}>{item}</div>;
})
}
</div>
);
}
}
3.1.7 static getDerivedStateFromError
此⽣命周期会在后代组件抛出错误后被调⽤。 它将抛出的错误作为参数,并返回⼀个值以更新 state;
3.1.8 componentDidCatch(error,info)
componentDidCatch() 会在“提交”阶段被调⽤,因此允许执⾏副作⽤。 它应该⽤于记录错误之类的情 况;
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下⼀次渲染可以显示降级 UI
return { hasError: true };
}
componentDidCatch(error, info) {
// "组件堆栈" 例⼦:
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logComponentStackToMyService(info.componentStack);
}
render() {
if (this.state.hasError) {
// 你可以渲染任何⾃定义的降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
4.事件处理
- 在JSX元素上添加事件,通过on*EventType这种内联⽅式添加,命名采⽤⼩驼峰式(camelCase)的形 式,⽽不是纯⼩写(原⽣HTML中对DOM元素绑定事件,事件类型是⼩写的);
- ⽆需调⽤addEventListener进⾏事件监听,也⽆需考虑兼容性,React已经封装好了⼀些的事件类 型属性;
- 使⽤ JSX 语法时你需要传⼊⼀个函数作为事件处理函数,⽽不是⼀个字符串;
- 不能通过返回 false 的⽅式阻⽌默认⾏为。你必须显式的使⽤ preventDefault;
⼀般不需要使⽤ addEventListener 为已创建的 DOM 元素添加监听器;
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使⽤ `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
// class 的⽅法默认不会绑定 this。如果没有绑定 this.handleClick 并把它传⼊
了 onClick,
// this 的值为 undefined。
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
2. 箭头函数,问题: 每次render都会创建不同的回调函数,如果该回调函数作为props传⼊⼦组
件,每次⼦组件都要re-render
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
// <button onClick={this.handleClick().bind(this)}>
Click me
</button>
);
}
}
4.1接收参数
- 事件对象 e 会被作为第⼆个参数传递;
- 通过箭头函数的⽅式,事件对象必须显式的进⾏传递;
- 通过 Function.prototype.bind 的⽅式,事件对象以及更多的参数将会被隐式的进⾏传递;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
5. 条件渲染
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
5.1 与运算符 &&
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
5.2 三元运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
5.3如何阻⽌组件渲染
function WarningBanner(props) {
//返回null,不渲染
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
5.4 列表
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不⽤index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使⽤索引⽤作为列表项⽬的 key 值;