我开发了一个小应用程序,用户可以在其中找到搜索输入,文件树和文本框。你可以在这个问题中找到一个例子。
基本上,它应该像这样工作:
点击<FileTree />
=&gt;中的README.md ./README.md
=&gt;内显示<TextBox />
点击./README.md
=&gt; 出现Morpheus图像。
正如您所看到的,图像未与<TextBox />
内的点击相关联,因此无法满足理想预期。
这让我感到困惑。
我知道点击事件需要在<TextBox />
内定义,并将道具传递给负责图像的组件。
但是,我很困惑,我应该如何传递道具和&#34;存储&#34;网址,因为图片是外部链接,对吗?
此外,只需要一张图像,不多张图片。
有人可以帮忙吗? 我是ReactJS的新手。有些事情对我来说有点混乱和新鲜。提前感谢您提供的帮助。
// TEXT BOX COMPONENT
class TextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
content: "Select A Node To See Its Name Here..."
};
this.changeContent = this.changeContent.bind(this);
}
changeContent(newContent) {
this.setState({
content: newContent
});
}
imageClick(newImage) {
console.log("TEXT INSIDE <TextBox /> WAS CLICKED");
}
componentWillReceiveProps(nextProps) {
this.setState({
content: nextProps.content
});
}
render() {
return (
<div className="padd_top">
<div className="content_box">{this.state.content}</div>
</div>
);
}
}
// SEARCH COMPONENT
class SearchEngine extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ""
};
this.inputChange = this.inputChange.bind(this);
}
inputChange(e) {
const content = e.target.value;
this.props.onChange(content);
}
render() {
return (
<input className="form-control" placeholder="Search the tree..." onChange={this.inputChange} />
);
}
}
// IMAGE COMPONENT
class DetailBox extends React.Component {
render() {
return (
<div>
<img alt="" src="https://media.makeameme.org/created/what-if-I-y0ivox.jpg" />
</div>
);
}
}
// FILE TREE COMPONENT
let data = [
{
type: "directory",
name: ".",
contents: [
{
type: "directory",
name: "./bin",
contents: [{ type: "file", name: "./bin/greet" }]
},
{
type: "directory",
name: "./lib",
contents: [{ type: "file", name: "./lib/greeting.rb" }]
},
{
type: "directory",
name: "./spec",
contents: [
{ type: "file", name: "./spec/01_greeting_spec.rb" },
{ type: "file", name: "./spec/02_cli_spec.rb" },
{ type: "file", name: "./spec/spec_helper.rb" }
]
},
{ type: "file", name: "./CONTRIBUTING.md" },
{ type: "file", name: "./Gemfile" },
{ type: "file", name: "./Gemfile.lock" },
{ type: "file", name: "./LICENSE.md" },
{ type: "file", name: "./README.md" }
]
}
];
// Icon file image for 'FileTree'
const FileIcon = () => {
return (
<div className="svg-icon">
<svg
id="icon-file-text2"
className="icon"
viewBox="0 0 32 32"
fill="currentColor"
width="1em"
height="1em"
>
<path d="M28.681 7.159c-0.694-0.947-1.662-2.053-2.724-3.116s-2.169-2.030-3.116-2.724c-1.612-1.182-2.393-1.319-2.841-1.319h-15.5c-1.378 0-2.5 1.121-2.5 2.5v27c0 1.378 1.122 2.5 2.5 2.5h23c1.378 0 2.5-1.122 2.5-2.5v-19.5c0-0.448-0.137-1.23-1.319-2.841zM24.543 5.457c0.959 0.959 1.712 1.825 2.268 2.543h-4.811v-4.811c0.718 0.556 1.584 1.309 2.543 2.268zM28 29.5c0 0.271-0.229 0.5-0.5 0.5h-23c-0.271 0-0.5-0.229-0.5-0.5v-27c0-0.271 0.229-0.5 0.5-0.5 0 0 15.499-0 15.5 0v7c0 0.552 0.448 1 1 1h7v19.5z" />
<path d="M23 26h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
<path d="M23 22h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
<path d="M23 18h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
</svg>
</div>
);
};
// Icon folder image for 'FileTree'
const FolderIcon = () => {
return (
<div className="svg-icon">
<svg
id="icon-folder"
className="icon"
viewBox="0 0 32 32"
fill="currentColor"
height="1em"
width="1em"
>
<path d="M14 4l4 4h14v22h-32v-26z" />
</svg>
</div>
);
};
// Icon arrow image for 'FileTree'
const TriangleDown = () => {
return (
<div className="svg-icon">
<svg
id="svg__icon--triangle-down"
viewBox="0 0 9 4.5"
fill="currentColor"
height="1em"
width="1em"
>
<path d="M0,0,4.5,4.5,9,0Z" />
</svg>
</div>
);
};
// Filters file 'name' and adds '/'
const formatName = name => {
return name.substr(name.lastIndexOf("/") + 1);
};
// Dummy data set
var root = data[0];
// Construction of FileTree
class FileTree extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNode: null
};
this.setActiveNode = this.setActiveNode.bind(this);
}
componentWillReceiveProps({ searchTerm }) {
this.setState({ searchTerm });
}
setActiveNode(name) {
this.setState({ activeNode: name });
this.props.liftStateUp(name);
}
render() {
return (
<div className="padd_top">
{renderTree(
this.props.root || root,
this.setActiveNode,
this.state.activeNode,
null,
this.state.searchTerm
)}
</div>
);
}
}
class Directory extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: true,
};
this.toggleDirectory = this.toggleDirectory.bind(this);
}
toggleDirectory() {
this.setState({ expanded: !this.state.expanded });
}
hasMatchingNodes() {
const filteredNodes = this.props.node.contents.filter(
(node) => {
return (
node.name.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) >
-1
);
});
console.log(this.props.searchTerm);
return filteredNodes.length > 0;
}
render() {
let node = this.props.node;
if (this.props.searchTerm && !this.hasMatchingNodes()) return null;
return (
<div className="directory-container">
<div className="directory">
<div
className=
{`directory__toggle ${ this.state.expanded ? "expanded" : ""}`}
>
<div onClick={this.toggleDirectory}>
<TriangleDown />
</div>
</div>
<div className="directory__icon" onClick={this.toggleDirectory}>
<FolderIcon />
</div>
<div className="directory__name" onClick={this.toggleDirectory}>
<div>{formatName(node.name)}</div>
</div>
</div>
{this.state.expanded
? node.contents.map((content, index) =>
renderTree(
content,
this.props.setActiveNode,
this.props.activeNode,
index,
this.props.searchTerm
)
)
: ""}
</div>
);
}
}
// Set class Active do selected file
const File = ({ name, setActiveNode, activeNode, searchTerm }) => {
if (searchTerm && name.toLowerCase().indexOf(searchTerm.toLowerCase()) < 0)
return null;
let isActive = activeNode === name;
let className = isActive ? "active" : "";
return (
<div className={className + " file"} onClick={() => setActiveNode(name)}>
<div className="file__icon">
<FileIcon />
</div>
<div className="file__name">{formatName(name)}</div>
{isActive && <div className="file__options">...</div>}
</div>
);
};
var renderTree = (node, setActiveNode, activeNode, index, searchTerm) => {
if (node.type === "file") {
return (
<File
key={index}
name={node.name}
setActiveNode={setActiveNode}
activeNode={activeNode}
searchTerm={searchTerm}
/>
);
} else if (node.type === "directory") {
return (
<Directory
key={index}
node={node}
setActiveNode={setActiveNode}
activeNode={activeNode}
searchTerm={searchTerm}
/>
);
} else {
return null;
}
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNode: ""
};
this.onChange = this.onChange.bind(this);
}
liftStateUp = (data) => {
this.setState({ activeNode: data });
};
onChange(data) {
this.setState({ searchTerm: data });
}
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine className="form-control" onChange={this.onChange} />
</div>
<div className="col-md-6">
<FileTree
liftStateUp={this.liftStateUp}
searchTerm={this.state.searchTerm}
/>
</div>
<div className="col-md-6">
<TextBox
content={this.state.activeNode}
onClick={this.onClickImage}
/>
</div>
<div className="col-md-6">
<DetailBox image={this.props.image} />
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
&#13;
* {
font-family: Helvetica;
background-color: #212830;
color: #9ea1b2 !important;
}
.directory {
padding-left: 10px;
padding-top: 1px;
padding-bottom: 1px;
display: flex;
flex-direction: row;
align-items: center;
}
.directory__toggle {
padding-left: 10px;
transform: rotate(-90deg);
}
.directory__icon {
padding-left: 10px;
}
.directory__icon {
padding-left: 10px;
}
.directory__name {
padding-left: 10px;
}
.directory-container {
padding-left: 10px;
}
.file {
padding-left: 50px;
padding-top: 1px;
padding-bottom: 1px;
display: flex;
}
.file__icon {
padding-left: 10px;
}
.file__name {
padding-left: 10px;
}
.file__options {
align-self: flex-end;
}
.active {
color: #ffffff;
background-color: #31343f;
}
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.svg-icon {
width: 1em;
height: 1em;
}
.expanded {
transform: rotate(0deg);
}
/** CONTENT BOX **/
.padd_top {
padding-top: 20px;
}
.content_box {
font-size: 12px;
white-space: pre-wrap;
background-color: #282c34;
border: solid 1px black;
padding: 20px;
color: #9da5ab;
min-height: 250px;
margin-bottom: 15px;
}
.text_color {
color: #21252b !important;
}
/** SEARCH **/
.form-control {
margin-top: 15px;
}
/** ARROW ANIMATION **/
.rotate {
animation: rotate-keyframes 1s;
}
@keyframes rotate-keyframes {
from {
transform: rotate(-90deg);
}
to {
transform: rotate(0deg);
}
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
&#13;
答案 0 :(得分:0)
仔细分析后,我发现您的代码缺少一些工作原因:
首先,在<TextBox/>
内你需要正确定义onClick事件,将src
作为道具传递给<DetailBox/>
,如下所示:
<div onClick={() => this.props.onClick()} className="content_box">
{this.state.content}
</div>
然后,在您的父组件<App/>
中,您可以定义一个州。我使用名称visible
定义了图像的状态,然后使用名为showImage()
的方法定义了图像的状态,以便在单击时更改状态。
由于您不希望图片具有“可嵌入性”,因此当您在true
内定义this.setState() { visible: true }
时,状态仅会更改为showImage()
。这样,每次点击都不会改变状态。
constructor(props) {
super(props);
this.state = {
activeNode: "",
visible: false
};
this.onChange = this.onChange.bind(this);
this.showImage = this.showImage.bind(this);
}
showImage() {
this.setState({ visible: true });
}
最后,不要忘记将onClick={this.showImage}
添加到<TextBox/>
组件和{ visible && <DetailBox src={this.props.src} /> }
,以便连接点击事件,从<TextBox/>
传递道具,连接{ {1}} src
更改图片的状态。像这样:
<DetailBox/>
我希望这有帮助!