本篇为楼主自己的React学习笔记,所有描述都是楼主自己的理解,不一定是正确的描述,请酌情理解,如能指出错误会相当感谢。
首先是React的包,适用于单页面应用时直接导入
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
//开发
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
//生产
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
//JSX编译器
有单页面也就有直接拉项目结构的,这和Vue一样
npx create-react-app my-app
cd my-app
npm start
1.JSX
这是一个JavaScript的语法扩展,能很好的适配React,在代码上提升视觉辅助作用,还能让React显示更多有用的错误和警告信息。
在jsx中嵌入表达式
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
同时jsx也是一个表达式,可以return一个JavaScript对象
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
2.元素渲染
渲染一个React元素,将其传入ReactDOM.render().
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
React元素是不可变对象,所以更新UI,其实为创建一个新的元素,再将其传入一次ReactDOM.render().
一个计时器案例
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);
React会自动检测查重然后只更新需要更新的部分,即传入一个UI树,只会更新实际改变了的内容,这样能消除一整类的BUG(Vue是不是也是这样的呢?忘了)
3.组件和Props
函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
class 组件(ES6)
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
以上两组件React中等效
渲染组件
const element = <Welcome name="Sara" />;
以及如何去渲染到页面上
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
注意:自定义组件名称必须以大写字母开头,小写会被视为原生DOM。
组合组件
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')
);
渲染组件的嵌套
提取组件
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}//Comment父组件
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}//Comment中提取出来的UserInfo组件
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}//以及进一步的Avatar组件
这样组件的嵌套,提高复用性,更容易维护,也更适合大型应用。
props的只读属性
组件不能修改自身的props,相当于不会修改入参的纯函数一般
而如下则不是一个纯函数
function withdraw(account, amount) {
account.total -= amount;
}
React规定所有组件都必须像纯函数一样保护它们的props不被修改。
4.State & 生命周期
因为props是只读的,为了给组件提供一个可修改的属性,所以有了state.
以下是一个计时器的案例
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
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')
);
使用state要注意的事:
1.不要直接修改state,而是使用setState(),只有构造函数是state唯一能赋值的地方。
2.state的更新可能是异步的
因为 this.props
和 this.state
可能会异步更新,所以不要依赖他们的值来更新下一个状态。
this.setState({
counter: this.state.counter + this.props.increment,
});// Wrong
以上代码可能会无法更新计数器,需要把操作写进函数
this.setState((state, props) => ({
counter: state.counter + props.increment
}));// Correct
3.state的更新会被合并
意思是state包含多个独立的变量,你可以单独修改其中每一个变量,不会影响其他变量的值。
4.数据向下流动
意思是父组件的state可以传给子组件的props,因为state是局部封装的,子组件的props的只读不会收到影响。同时引用相同组件的三个兄弟对象的值也都是各自自己的,不会相互影响。
生命周期
1.组件初始化(initialization)
也就是constructor构造方法,需要继承React.Component这个类,才有了render()类,生命周期方法才能用。
super(props)用来调用基类Component的构造方法,把props注入给子组件,而state则是手动设置的,这也是为什么props只读,state可以修改。
2.组件的挂载(Mounting)
此阶段分为componentWillMount,render,componentDidMount三个时期。
componentWillMount:在组件挂载到DOM之前调用,只会被调用一次,此类中写this,setState不会调用,而且这里的内容也能写进constructor,所以很少用。
render:根据props和state,return一个React元素,不负责渲染工作,是纯函数。
componentDidMount:组件挂载到DOM后调用,只会调用一次。
3.更新(update)
React更新机制:setState引起的state更新或者父组件的render引起的props更新,都会重新render.
此阶段分为componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate
componentWillReceiveProps(nextProps):在props引起的组件更新中调用,可以用来判断props值有没有改变,如果没有改变可以不执行重新渲染操作。
shouldComponentUpdate:因为父组件重新render会让所有子组件都重新渲染,所以给Child组件加上shouldComponentUpdate()函数来优化这个情况。如此父组件更新后子组件如果没有变化就不会重新渲染。该方法同样也适用子组件自己setState()后是相同值的情况。
class Child extends Component {
shouldComponentUpdate(nextProps){
if(nextProps.someThings === this.props.someThings){
return false
}
}
render() {
return <div>{this.props.someThings}</div>
}
}//优化后子组件没有改变便不会重新渲染
componentWillUpdate:调用render前执行,少用。
render:同上文
componentDidUpdate:在组件更新后调用,可以操作组件更新.
4.卸载阶段
此阶段只有componentWillUnmount
componentWillUnmount:在组件被卸载前调用,可以在这里清理定时器,componentDidMount中手动创建的DOM等,避免内存泄漏。
React v16.4+ 的生命周期
变更原因:
原React生命周期在Fiber的到来下,如下生命周期函数的调用不再是必定只调用一次了。
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
以上生命周期函数被归类到Fiber新的更新阶段中的Reconciliation Phase(和解阶段)。在这个阶段中每走一个时间片会重新检测有没有紧急任务,如果有就会放弃当前执行的函数去完成紧急任务,之后再重新走之前没执行完的生命周期方法。
重点是这个是重新执行方法,而不是从断开部分执行,所以如果其中有ajax请求的话就会多次发起,显然这是不对的。
新的生命周期:getDerivedStateFromProps,getSnapshotBeforeUpdate
getDerivedStateFromProps:取代了之前的componentWillMount,componentWillReceiveProps,componentWillUpdate。作为一个静态函数,只能做无副作用的操作,局限在根据props和state决定新的state操作之中。React v16.4+版本后在组件挂载和更新之前都必定被调用。应返回一个对象来更新状态,或者是返回Null不更新任何内容。该方法要在声明是加上static。
getSnapshotBeforeUpdate:被调用于render之后,它可以在你能读取不能使用DOM的时候在可能更改之前从DOM中捕获一些信息,可以用来记录滚动位置等。此生命周期返回的值都会作为参数传给componentDidUpdate()。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//我们是否要添加新的 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) {
//如果我们有snapshot值, 我们已经添加了 新的items.
// 调整滚动以至于这些新的items 不会将旧items推出视图。
// (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}