React入门笔记
创建一个react单页面
npx create-react-app <项目名>
cd <项目名>
yarn start # 部署前端服务(使用yarn作为包管理器 )
执行yarn run build
会在build目录中生成打包版本
其他命令:
yarn start
Starts the development server.
yarn build
Bundles the app into static files for production.
yarn test
Starts the test runner.
yarn eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
创建一个简单页面:
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<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>
<div id="app"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world</h1>,
document.getElementById('app')
);
</script>
</body>
</html>
需要引入babel和react相关的包
实时渲染一个react对象
<div id="root"></div>
<script type="text/babel">
function tick() {
const element = (
<div>
<h1>Hello, world</h1>
<h2>Is is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
)
}
setInterval(tick, 1000);
</script>
这个例子会在setInterval()
回调函数,每秒都调用 ReactDOM.render()
。
组件
创建React的组件有两种方式
-
函数组件
function foo(props) { return ( <h1>hello, {props.name}</h1> ) }
-
类组件
class foo extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ) } }
以上两种定义方式定义的组件在React中是等效的
有一点需要注意:组件的名称必须开头大写
渲染组件
React组件吃了是DOM标签,也可以是自定义标签
const element = <div />; // html自带的div标签
const element = <Foo name="Sara" />; // 自定义的Foo标签
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
function Foo(props) {
return <h1>Hello, {props.name}</h1>
}
ReactDOM.render(
<Foo name="Sara"/>,
document.getElementById('root')
)
网页效果:
拆分组件
对于一个组件,可以通过带className
属性的子标签作为该组件的子组件来把当前组件拆分
function SuperComponent(props) {
return (
<div className="SuperComponent">
<img className="ChildComponent"
src={props.info.imgUrl}
alt={props.info.name}
/>
</div>
);
}
const info = {
"imgUrl": "https://img1.gtimg.com/gamezone/pics/hv1/23/149/2287/148750193.jpg",
"name": "img",
}
function Foo(props) {
return (
<div className="Foo">
<Hello />
<SuperComponent info={info}/> // 给props中的info赋值
</div>
)
}
提取子组件
当一个组件有着过多的子组件就会产生复杂的嵌套关系,这个时候为了复用子组件就需要提取子组件
function ChildComponent(props) {
return (
<img className="ChildComponent"
src={props.info.imgUrl}
alt={props.info.name}
/>
)
}
此时父组件中的子组件相关代码就可以用子组件标签代替了
function SuperComponent(props) {
return (
<div className="SuperComponent">
<ChildComponent info={props.info}/>
</div>
);
}
定义函数式组件时要注意:所有React组件都要像纯函数一样保护他们的props不被更改
提取子组件到文件
当一个子组件过多时,可以将组件单独写到一个js文件里
如 Clock.js
import React from 'react'; // 导入React相关的包
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerIO = setInterval(
() => {
this.setState({
date: new Date()
});
},
1000
);
}
componentWillUnmount() {
clearInterval(this.timerIO);
}
render() {
return (
<div>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
export default Clock;
这样就能在index.js
中引用
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Clock from './Clock'
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<Clock />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);
State & 生命周期
在最开始定义了一个动态显示时间的组件,现在将其封装
function Clock(props) {
return (
<div>
<h1>hello world</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
)
}
但是这个组件的date并不会实时更新,想要做到实时更新组件,就可以用state
state与props类似,但是state是组件私有的,并且完全受控于当前组件
使用state需要将函数式组件转换为类式组件
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
并且将this.props.date
改为this.state.date
之后添加构造函数为this.state
赋初始值
constructor(props) {
super(props);
this.state = {date: new Date()};
}
之后<Clock />
也不再需要date
属性了,它已经定义在state里了
但是此时组件依然不会自动更新,因此需要为其第一次被渲染时设置一个计时器。这在React中被称为挂载(mount
),而当DOM中的组件被清除时还需要清除计时器,这被称为卸载(unmount
)
在组件中可以声明一些函数来执行挂载和卸载
componentDidMount() {
}
componentWillUnmount() {
}
这些方法被称为生命周期方法
以下代码即可动态渲染<Clock />
组件
tick() {
this.setState({ // 使用setState方法更新state属性
date: new Date() // 动态更新state的date
});
}
componentDidMount() { // 挂载函数执行setInterval方法
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() { // 卸载函数执行clearInterval方法来清除
clearInterval(this.timerID)
}
使用State的注意事项
不要直接修改State
要注意一点:不要直接修改组件的state,而是使用setState()方法修改值
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
State的更新可能是异步的
React可能会把多个setState()
合并到一起调用,因此this.props
和this.state
的更新可能是异步的,放在一起使用很容易产生bug
例如:
this.setState({
counter: this.state.counter + this.props.increment,
});
这个例子中就将state和props放在一起使用了,这样的写法是错误的
要解决这个问题可以让setState()
接收一个函数而不是一个对象
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
数据向下流动
因为state是组件私有的,因此一个组件的父组件或者子组件是无法知道一个组件有没有state的
组件可以吧state作为props向下传递到他的子组件中,但是这样子组件本身无法知道一个state是来自父组件还是来自本身的
事件处理
- React的事件采用小驼峰命名规则
- 使用JSX语法时需要传入一个函数作为事件处理函数而非传入一个字符串
传统html:
<button onclick="activateLasers()">
Activate Lasers
</button>
jsx:
<button onClick={activateLasers}>
Activate Lasers
</button>
React还有一个不同点:不能通过返回false阻止默认行为,而要通过显示使用preventDefault
例如:
html
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
jsx
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
在这里,e
是一个合成事件。React 根据 W3C 规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。如果想了解更多,请查看 SyntheticEvent
参考指南。
一个按钮组件
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`, 这个bind必不可少
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
)
}
}
js语法中class方法不会默认绑定this
,如果忘了绑定的话,调用函数时调用的值为undefine
如果觉得使用 bind
很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:
class LoggingButton extends React.Component {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
// 注意: 这是 *实验性* 语法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
建议使用class fields,使用lambda可能会进行额外的渲染影响性能
向事件处理传递参数
有两种方式向处理函数传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind
来实现。
在这两种情况下,React 的事件对象 e
会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。
条件渲染
根据条件分支进行不同的渲染
下面是一个例子,会根据props中isLoggedIn
的值进行不同的渲染
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return (
<h1>Welcome back!</h1>
);
} else {
return (
<h1>Please sign up.</h1>
);
}
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);
当然这样isLoggedIn
的值就固定了,现在使用一个组件来使得isLoggedIn
的值可以通过点击按钮改变
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
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>
)
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('app')
);
短路表达式
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。
之所以能这样做,是因为在 JavaScript 中,true && expression
总是会返回 expression
, 而 false && expression
总是会返回 false
。
因此,如果条件是 true
,&&
右侧的元素就会被渲染,如果是 false
,React 会忽略并跳过它。
三目运算符
除了短路表达式,jsx也支持三目运算符condition ? true : false
。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
需要注意的是三目运算符的滥用会导致代码可读性变低,如果条件很复杂,建议不使用三目运算符
阻止渲染
如果需要隐藏组件,那么会用到阻止渲染使render
方法返回null
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
组件的render
方法返回null
并不会影响组件的生命周期
渲染列表
React渲染列表是用的是js内置的map()
方法
const list = [1, 2, 3, 4, 5];
const listItems = list.map((item) =>
<li>{item}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
)
通常会在一个组件中渲染列表
function List(props) {
const items = props.items;
const listItems = items.map((item) =>
<li>{item}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
function App(props) {
return (
<div>
<List items={numbers}/>
</div>
);
}
需要注意的是渲染列表时需要制定一个key
来作为列表下标
const listItems = items.map((item) =>
<li key={number.toString()}>{item}</li>
);
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key
key设置的位置:通常在map()
函数中需要给标签设置key
key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key
属性的值,请用其他属性名显式传递这个值:
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
上面例子中,Post
组件可以读出 props.id
,但是不能读出 props.key
。
jsx允许在打括号中嵌入任何表达式,因此可以内联使用map()
函数
function List(props) {
const items = props.items;
return (
<ul>
{items.map((item) =>
<li key={item.toString()}>{item}</li>
)}
</ul>
);
}
路由
安装
yarn add react-router-dom
使用
-
导入核心组件
-
导入
Router
、Route
和Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
-
使用
Router
包裹整个应用并设置路由入口function App() { return ( <Router> <div className="App"> <div>React路由基础</div> {/*指定路由入口*/} <Link to="/first">页面一</Link> </div> </Router> ); }
-
使用
Route
组件配置路由规则和要展示的组件function App() { return ( <Router> <div className="App"> <div>React路由基础</div> {/*指定路由入口*/} <Link to="/first">页面一</Link> {/*指定路由出口*/} <Route path="/first" component={First}></Route> </div> </Router> ); }
新建一个组件
//First.js function First() { return ( <div> <p>页面一的内容</p> </div> ) } export default First;
然后在主页组件中导入
// App.js import First from './component/First' // 导入组件 function App() { return ( <Router> <div className="App"> <div>React路由基础</div> {/*指定路由入口*/} <Link to="/first">页面一</Link> <Route path="/first" component={First}></Route> </div> </Router> ); }
-
运行项目
点击
页面一
,下面显示出了页面一的内容
基本使用
常用的Router
组件有两种,分别是HashRouter
和BrowserRouter
,后者更常用
- HashRouter:使用url的哈希值实现(localhost/#/pathname)
- BrowserRouter:使用H5的history API实现(lcoalhost/pathname)
执行过程
- 点击
<Link>
组件之后浏览器地址栏被修改 - React路由监听地址栏url的变化
- React路由内部便利所有Router组件,使用路由规则(path)与pathname进行匹配
- 匹配成功就展示对应组件内容
编程式导航
通过js代码实现页面跳转
使用 this.props.history.push('/pathname')
就能实现路由的跳转
修改First.js的代码
// First.js
function First(props) {
const handleLogin = () => {
props.history.push('/home')
}
return (
<div>
<p>页面一的内容</p>
<button onClick={handleLogin}>去Home</button>
</div>
)
}
export default First;
然后创建新的页面组件
// Home.js
function Home() {
return (
<div>
<h2>我是后台首页</h2>
</div>
)
}
export default Home;
并配置其路由
// App.js
import First from './component/First'
import Home from './component/Home'
function App() {
return (
<Router>
<div className="App">
<div>React路由基础</div>
{/*指定路由入口*/}
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
<Route path="/home" component={Home}></Route>
</div>
</Router>
);
}
这样点击去Home按钮时就会跳转到Home页面
返回上一个页面可以使用props.history.go(-1)
默认路由
默认路由:进入页面时就会匹配的路由
<Route path="/" component={Home}></Route>
path为"/"
的路由就是默认路由
静态路由
安装
yarn add react-router-config
-
创建静态路由组件(类似于vue的
<router-view>
)import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import First from '../component/First' import Home from '../component/Home' const routes = [ { path: '/', component: Home, exact: true }, { path: '/first', component: First, exact: true } ] export default function RouteView() { return ( <div> <Router> <RootRoute routeConfig={routes}/> </Router> </div> ) } function RootRoute({routeConfig, basePath=''}) { if (routeConfig.length === 0) { return <></> } const rt = routeConfig.map((it, idx) => { const {Comp, Child, path, ...rest} = it; const newPath = (basePath + path).replace(/\/\./g, '/') const isExact= 'exact' in it ? it.exact : true return ( <Route key={newPath} {...rest} exact={isExact} path={newPath} render={(value) => { let crt = <></> if (Array.isArray(Child) && Child.length > 0) { crt = RootRoute({Child, newPath}) } return ( <Comp {...value}> {crt} </Comp> ) }} /> ) }) return ( <Switch> {rt} </Switch> ) }
-
在App.js中进行引入
import './App.css'; import RouteView from './router/index' function App() { return ( <div className="App"> <div>React路由基础</div> <RouteView/> </div> ); } export default App;
useReducer
基础用法
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
}
例如
function reducer(state, action) {
if (action.type === 'incremented_age') {
return {
age: state.age + 1
};
}
throw Error('Unknown action.');
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
return (
<>
<button onClick={() => {
dispatch({ type: 'incremented_age' })
}}>
Increment age
</button>
<p>Hello! You are {state.age}.</p>
</>
);
}
其中reducer函数也可以用switch来优化
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
本文作者:七つ一旋桜
本文链接:https://www.cnblogs.com/poifa/p/15216481.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步