一、首先全局安装create-react-app
在当前目录下面安装: npm install -g create-react-app
npx create-react-app my-app 后出现如图所示问题:
所以切换node版本
1. nvm ls //查看当前下载的node版本
2. nvm install 16.14.0//安装指定版本的Node.js
3. nvm use 16.14.0 //切换node版本到16.14.0
检查当前的Node.js和npm版本:
继续执行npx create-react-app my-app命令:
找到日志:
找到对应的文件:
依然报错;
- 文件命名从
React App更改为ReactApp; - 删除npm-cache文件,重新安装node版本;
- 安装
Node.js版本为16.12.0npm为8.1.0; - 重新执行
npm install -g create-react-app->npx create-react-app my-app; Ok
之前一个是文件夹命名问题,一个是安装Node.js版本是LTS,重新安装切换node.js版本即可。
二、概念学习
1. Hello World
首先在html文件中搭好html结构;
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CDN连接</title>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div>CDN 连接使用</div>
<div id="root"></div>
<script type="text/babel" src="./index.js"></script>
</body>
在index.js文件中:
ReactDom.render(
<h1>hello world!!</h1>,
document.getElementById('root')
);
因为使用JSX语法,所以引用babel插件
2. JSX简介
JSX需要搭配babel使用,Babel将JSX编译成React.createElement调用。
有关JSX具体介绍参考JSX 介绍 - React 中文网 (caibaojian.com.cn)
3. 元素渲染
把一个React元素渲染到root DOM节点,需要把他们传递给ReactDOM.render()方法:
const element = <h1>hello</h1>;
ReactDOM.render(element,document.getElementById('root'));
React元素不可突变,创建元素之后不能修改其子元素或任何属性,更新UI的唯一方法是创建一个新的元素,将其传入ReactDOM.render()方法。
React DOM只更新必须要更新的部分,即只更新有变动的部分; React DOM将元素及其子元素与之前的版本逐一对比,并只对必须更新的DOM进行更新。
4. 组件(Components)和属性(Props)
组件分为函数式组件和类组件
//1. 函数式组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
//2. ES6定义一个组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
React元素既可以表示DOM标签,也可以表示用户自定义的组件:
//1. React元素表示DOM标签
const element = <h1>Hello, {name1}</h1>;
//2. React元素表示定义的组件(函数组件)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name = 'xll'/>;
React遇到代表用户组件的元素时,将JSX属性以单独对象的形式传递给相应的组件,称其为‘props’对象。 合成组件,组件中可以引用组件,可以把同样的组件抽象到任意层级,一个按钮一个表单,一个对话框,一个屏幕等。
//1. 函数组件Welcome
function Welcome(props) {
return (<div>
<h1>Hello, {props.name}</h1>
<h1>My age is {props.age}</h1>
</div>);
}
//2. 函数组件App
function App() {
return (<div>
<Welcome name = 'xll' age = '23'/>
<Welcome name = 'dwz' age = '25'/>
</div>);
}
问题: 组件(Components) 和 属性(Props) - React 中文网 (caibaojian.com.cn)
React不能直接渲染对象,应该渲染到具体的属性,如:
props.user是一个对象,应该渲染props.user.name或者props.user.avatarUrl
5. 状态(State)和生命周期
实现上图中的秒钟,初始代码如下:
function Clock(props) {
return (
<div>
<h1>Hello world!</h1>
<h2>It is {props.date.toLocaleTimeString()}</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()}/>,
document.getElementById('root')
);
}
setInterval(tick,1000);
理想情况下,应该只引用一个Clock组件,自动计时并更新。可以通过添加state到Clock组件,使用类组件可实现。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//在类中添加生命周期方法
componentDidMount() {
//Clock第一次渲染到DOM时,挂载componentDidMount
//在挂载时,设置时钟
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
//Clock产生的DOM销毁时,卸载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')
);
this.setState();是固定函数,用来设置state状态
setState()不能直接修改state状态,只能使用上述方法;- 唯一可以分配
this.state的地方是构造函;state(状态)更新可能是异步的;state(状态)更新会被合并,当调用setState()时,sate可能包含几个独立的变量,如下:
this.state = {
posts: [],
comments: []
};
当调用setState()时,更新posts会自动同步到state(状态中)。
state(状态)被称为本地状态或封装状态的原因,不能被拥有设置它的组件以外的组件访问,即谁设置谁有权限访问,但可以组件可以将state(状态)向下传递,作为其子组件的props(属性)。如:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>
}
<FormattedDate date={this.state.date}/>
在React应用中,可以在有状态的组件中使用无状态组件,也可以在无状态组件中使用有状态组件。
6. 处理事件
通过
React元素处理事件与在DOM元素上处理事件非常相似。但存在如下区别:
React事件使用驼峰命名。- 通过
JSX,传递一个函数作为事件处理程序,而不是一个字符串。
在HTML中使用事件:
<button onclick="activateLasers()">
Activate Lasers
</button>
在React中使用事件:
<button onclick={activateLasers}>
Activate Lasers
</button>
在React中使用事件,必须明确调用preventDefault来阻止默认行为。
使用函数组件:
function ActionClick(e) {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
ReactDOM.render(
<ActionClick/>,
document.getElementById('button')
)
实现效果:
使用类组件:
class ActionClick extends React.Component {
handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
render() {
return (
<a href="#" onClick={this.handleClick}>
Click me
</a>
);
}
}
ReactDOM.render(
<ActionClick/>,
document.getElementById('button')
)
使用React时,一般不需要调用addEventListener事件监听器,只需要在元素初始被渲染时,提供一个监听器。实现on和off状态切换,如下:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);//绑定this
}
handleClick() {
this.setState(prevSate => ({
isToggleOn: !prevSate.isToggleOn
}))
}
render() {
return (
<button onClick={this.handleClick} style={this.state.isToggleOn ? {color:'red',backgroundColor:'blue'}:{color:'blue',backgroundColor:'red'}}>
{this.state.isToggleOn ? 'on' : 'off'}
</button>
);
}
}
ReactDOM.render(
<Toggle/>,
document.getElementById('root')
);
在JavaScript中,类方法默认是没用绑定的,如果未使用this.handleClick = this.handleClick.bind(this);,this是undefined;除了上述显示的绑定this以外,还可以使用箭头函数的this指向属性,箭头函数的this指向定义箭头函数的地方,所以:
handleClick = () => {
console.log('this is :',this)
}
实现事件函数的传参:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
需要注意:箭头函数不需要显示绑定this,但事件对象e必须显示传递;而使用bind显示绑定this之后,事件对象e无需显示传递。
7. 条件渲染
在
React中,创建不同大的组件封装不同的行为,根据需求只渲染其中一些。
function UserGreeting(props) {
return (
<h1 style={{ backgroundColor: 'pink' }}>Welcome back!</h1>
);
}
function GuestGreeting(props) {
return (
<h1 style={{ backgroundColor: 'blue' }}>Please sign up!!!</h1>
);
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return (
<UserGreeting/>
);
} else {
return (
<GuestGreeting/>
);
}
}
ReactDOM.render(
<Greeting isLoggedIn={true}/>,
document.getElementById('root')
);
以上内容根据isLoggedIn取值来渲染不同组件。
示例7.1:考虑使用按钮点击控制组件渲染,代码如下:
class ClickButton extends React.Component {
constructor(props) {
super(props);
this.state = { isLoggedIn: true };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isLoggedIn: !prevState.isLoggedIn
}))
}
render() {
return (
<div>
<button onClick={this.handleClick}>
{this.state.isLoggedIn ? 'true' : 'false' }
</button>
<Greeting isLoggedIn={this.state.isLoggedIn}/>
</div>
);
}
}
ReactDOM.render(
<ClickButton/>,
document.getElementById('root')
);
render函数里面return只能包含一个根节点!!,上述代码效果如下:
示例7.2:状态组件LoginControl,根据条件渲染不同组件LoginButton和LogoutButton。
function LoginButton(props) {
return (
<button onClick={props.onClick} style={{color:'red',backgroundColor:'blue'}}>
登录!
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick} style={{color:'purple',backgroundColor:'pink'}}>
Logout!
</button>
);
}
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.state = {isLoggedIn: false};
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
}
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>
);
}
}
ReactDOM.render(
<LoginControl/>,
document.getElementById('root')
);
示例1是一个按钮组件,这里使用了两个不同的按钮组件。效果如下:
除了上述使用
if else有条件的渲染组件以外,开可以使用&&以及三目运算符实现有条件的渲染组件。
示例7.3:&&运算符
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
{unreadMessages.length>0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
)
}
const messages = ['React','JavaScript','Hello','XLL'];
ReactDOM.render(
<Mailbox unreadMessages={messages}/>,
document.getElementById('root')
);
示例7.4:三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick}/>
): (
<LoginButton onClick={this.handleLoginClick}/>
)}
</div>
);
}
防止组件渲染,可以使用return null,如:
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div style={{ color: 'red' }}>
Warning!!!
</div>
);
}
从组件的render方法返回null不会影响组件生命周期方法的触发。componentWillUpdate和componentDidUpdate仍然会被调用。
8. 列表&key
创建列表组件NumberList
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增加一个独一无二的字符串,通常使用数据中的id来作为元素的key,如果数据中没有id,可以使用元素索引index来作为key值。但是列表项目顺序发生变化时,用索引index来作为key值,会导致性能变差,可能引起组件状态的问题。
为什么key是必须的??
对子节点进行递归,在列表末尾增加新元素,更新开销比较小,比如:
在列表表头增加新原色,更新开销比较大,比如:
React不会保留<li>Duke</li>和<li>Villanova</li>,会重建每一个元素,会带来性能问题,增加不必要的重建操作。为了解决上述问题,React引入key属性,使用key来匹配原有树上的子元素以及最新树上的子元素。key不需要全局唯一,但是在当前列表中需要保持唯一,也可以使用元素在数组中的下标作为key(元素不进行重新排序时比较合适,如果有顺序修改,diff就会变慢)
用key提取组件,如果提取出一个ListItem组件,应该把key保留在数组中的这个<ListItem />元素上,而不是放到ListItem组件中的<li>元素上。
如果需要使用key属性的值,用其他属性名显式传递这个值。
JSX可以在大括号中嵌入任何表达式,可以内联map()返回的结果:但map()嵌套太多层级最好提取。
return (
<div>
{
<ul>
{props.posts.map((post) =>
<Lis key={post.id} title={post.title} />
)}
</ul>
}
<div/>
);
9. 表单
受控组件: 使用JavaScript函数可以很方便的处理表单的提交,同时还可以访问用户填写的表单数据。在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState()来更新。
对于受控组件来说,输入的值始终由React的state驱动。
input标签textarea标签select标签
1. <input type="text" value={this.state.value} onChange={this.handleChange}/>
2. <textarea value={this.state.value} onChange={this.handleChange} />
3. <select value={this.state.value} onChange={this.handleChange}>
非受控组件: 有时使用受控组件比较麻烦,所以可以用非受控组件代替:Formik
10. 状态提升
多个组件需要反映相同的变化数据,可以将共享状态提升到最近的共同父组件中。
上面的两个输入框应该同时影响两个输入框下方的提示信息。第一个输入框输入摄氏温度,第二个输入框输入华式温度,无论输入摄氏温度还是华氏温度,两处提示信息应该同步显示。 如下所示:
在任意一个框输入数值,都可以控制下列显示信息。
父组件:Calclulator
将需要改变的变量放到Calculator中,
使用
this.setState({scale: 'c', temperature});进行改变。
11. 组合VS继承
使用组合而非继承来实现组件间的代码重用。
有点类似插槽slot的概念,但是React中没有槽的概念限制,可以将任何东西作为props进行传递。
自行约定组件,比如:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left" style={{color:'blue'}}>
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
在上述代码中预留了props.left和props.right两个位置。
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat/>
}/>
);
}
12. React哲学
使用
React构建一个应用,通过React构建一个可搜索的产品数据表格来深刻领会React哲学。
假设JSON API返回数据为:
[ {category: "Sporting Goods", price: "49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "199.99", stocked: true, name: "Nexus 7"} ];
第一步:将设计好的UI划分为组件层级
根据单一功能原则判定组件范围,一个组件原则上只能负责一个功能。
第二部:用React创建一个静态版本
确定好组件层级,用已有的数据模型渲染一个不包含交互功能的UI。将渲染UI和添加交互两个过程分开。
当应用比较简单的时候使用自上而下的方式更加方便;对于大型的项目,自下而上的构建,同时为底层组件编写测试。