퓨즈[Fusetools] 문서[Docs] 번역

  1. Home
  2. 퓨즈[Fusetools] 문서[Docs] 번역
  3. 튜토리얼
  4. 4. 네비게이션과 라우팅

4. 네비게이션과 라우팅

소개

이전 챕터에서 우리는 앱의 뷰를 여러개의 컴포넌트로 나누었습니다. 이는 멋진 확장 가능한 아키텍처를 향한 큰 발전이었습니다. 우리는 PageControl을 사용하여 탐색에 대해 간략히 알아봤습니다. PageControl은 페이지를 다시 의미있는 방식으로 연결하기 위한 첫 단계로서 잘 작동했습니다.

PageControl은 두 개의 뷰를 서로 겹쳐서 “나란히” 표시하려는 경우 훌륭하게 작동하지만 우리가 생각하던 것과는 거리가 멉니다. 예를 들어, EditHikePage는 실제 편집을 위해 크게 유용하지는 않습니다. 따라서 단순히 뷰를 스와이프하는 것은 의미가 없습니다. 대신, 홈페이지에서 편집할 수 있는 하이킹 중 하나를 선택하면 해당 뷰로 이동하고, 필요할 때까지 EditHikePage를 유지할 필요가 없다면 그게 더 나을 것입니다.

Fuse는 네비게이터 및 라우터 클래스를 사용하여 이 모든 것을 처리할 수 있는 도구를 제공하고 몇 가지 간단한 개념을 제공합니다. 이번 챕터에서는 앱에 단계별로 적용할 때 이러한 것들을 살펴보겠습니다. 그럼 시작합시다!

이 챕터의 최종 코드는 여기에서 볼 수 있습니다.

네비게이터(Navigator)로 마이그레이션 하기

PageControl과 마찬가지로 Navigator는 탐색용 컨테이너입니다. 이것은 우리가 탐색할 수 있는 컴포넌트(일반적으로 페이지)를 포함할 수 있는 멋진 컨트롤입니다. 그러나 PageControl과 달리 Navigator는 템플릿을 사용하여 필요에 따라 하위 컴포넌트를 인스턴스화 합니다. 이를 통해 네비게이터는 필요에 따라 페이지를 인스턴스화하고 재활용하여 귀중한 시스템 자원을 보존할 수 있습니다. 또한 Navigator는 기본적으로 PageControl과 같이 페이지 사이를 스와이프하는 것을 지원하지는 않습니다. 하지만 이는 하위 항목 간의 풍부한 관계를 표현할 수 있기 때문에 좋습니다.

그렇다면 Navigator를 사용할 때 탐색할 컴포넌트를 어떻게 알 수 있나요? 우리는 다음 섹션에서 라우터(Router)에 대해 다룰 것입니다. 본질적으로, 이러한 개념을 설명하는 가장 좋은 방법은 실제로 작동하는 것을 보는 것입니다. 일단 네비게이터가 필요합니다. PageControl을 Navigator로 바꾸는 것으로 시작합시다.

먼저, MainView.ux를 열어 PageControl을 Navigator로 간단히 바꿉시다:

1
2
3
4
5
6
7
8
<App>
    <ClientPanel>
        <Navigator>
            <HomePage />
            <EditHikePage />
        </Navigator>
    </ClientPanel>
</App>

네비게이터가 자식 인스턴스 대신 인스턴스의 템플릿을 필요로 하기 때문에 이를 업데이트해야 합니다. 다행스럽게도 Fuse를 사용하면 다음과 같이 쉽게 만들 수 있습니다. 우리가 해야할 일은 Navigator의 각 자식에 ux:Template 속성을 추가하는 것입니다:

1
2
3
4
5
6
7
8
<App>
    <ClientPanel>
        <Navigator>
            <HomePage ux:Template="home" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>

기본적으로, 이러한 각 속성은 지정된 키(이 경우 home 및 editHike)에 대해 네비게이터가 관련 클래스를 인스턴스화하게 하려는 것입니다. 따라서 네비게이터가 홈으로 이동하라는 요청을 받으면 HomePage 인스턴스를 인스턴스화하고(기존에 없는 경우) 해당 페이지로 이동합니다. 마찬가지로 editHike로 이동하라는 메시지가 표시되면 EditHikePage 인스턴스가 인스턴스화되지 않은 경우 해당 인스턴스를 인스턴스화하고 이동합니다. 각 키가 고유하다는 점을 고려하여 원하는 템플릿을 추가할 수 있습니다. (그렇지 않은 경우 네비게이터는 주어진 키에 사용할 템플릿을 알 수 없을 것입니다)

이제 이것을 저장하면 미리보기가 업데이트 되지만 페이지가 사라집니다! 이것은 네비게이터가 자식 컴포넌트를 생성하기 위해 사용할 템플릿을 지정했기 때문입니다. 하지만 실제로 아무것도 인스턴스화되지 않았습니까? 다음 섹션에서 살펴 보겠지만 특정 경로로 이동하여 이 작업을 수행합니다. 그러나 Navigator는 아직 자식 중 하나를 탐색하지 않은 경우 기본 자식을 만드는 데 사용할 기본 경로를 지정하는 기능도 지원합니다. 이는 우리의 사용 사례에 가장 적합한 일반적인 경우입니다. 시작과 함께 네비게이터가 홈 페이지를 표시하고 편집을 위해 하이킹을 선택하면 EditHikePage로 이동하기를 원합니다.

Navigator의 기본 경로를 지정하려면 Navigator에서 처음 인스턴스화할 때 사용할 템플릿의 키를 지정하는 DefaultPath 속성을 추가해야 합니다:

1
2
3
4
5
6
7
8
<App>
    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" />
            <EditHikePage ux:Template="editHike" />
        </Navigator>
    </ClientPanel>
</App>

이제 파일을 저장하면 홈페이지가 우리의 기대처럼 표시될 것입니다. 멋집니다! 원한다면 editHikePage를 보여주기 위해 DefaultPath를 editHike로 변경할 수도 있습니다. 부담없이 사용해보세요!

라우터(Router)로 라우팅 하기

네비게이터와 템플릿을 사용하고 있으므로, 네비게이터에게 어느 페이지를 탐색할 것인지 알려줄 차례입니다. 이제 라우터가 필요한 순간이죠!

라우터는 라우팅을 관리합니다. 라우팅은 앱에서 어디로 이동할지 지정하고 실제 도착하게 하는 것입니다. 보다 구체적으로, 라우터는 우리가 탐색하고자 하는 일종의 “목표”를 결정하는 경로를 사용하여 응용 프로그램을 탐색하고 가능하면 추가 데이터를 포함하여 해당 응용 프로그램을 탐색합니다. 실제로 라우팅에 참여할 컨트롤인 라우터 아울렛을 찾기 위해 시각적 트리를 검색하여 네비게이션을 수행합니다. 예를 들어, PageControl과 Navigator는 모두 라우터 콘센트입니다. 라우터는 이전에 있었던 경로의 내역을 추적할 수 있으며 원하는 경우 다시 탐색할 수 있습니다.

이제 많은 것이 가능합니다. 실제로 라우터는 많은 일을 할 수 있으며 매우 강력합니다! 사용하기 쉽고 직관적이기 때문에 라우터에 앱을 추가하고 두 페이지를 탐색하기 위해 어떻게 사용할 수 있는지 살펴보도록 하겠습니다.

우리가 할 첫 번째 일은 실제로 Router 인스턴스를 생성하는 것입니다. 여기서는 전체 앱에 대해 하나의 라우터만 필요합니다. 이것은 아주 일반적입니다. 그래서 Router를 App 클래스의 최상위에 추가합니다:

1
2
3
4
5
6
7
<App>
    <Router />

    <ClientPanel>
        <Navigator DefaultPath="home">

        ...

이제 Router 인스턴스를 생성했으므로, EditHikePage로 이동할 수 있도록 HomePage에 이 인스턴스를 삽입해야 합니다. 이를 수행하는 데는 몇 가지 방법이 있지만 이번 경우에는 종속성(dependency)을 사용하는 것이 최선의 방법입니다. 종속성을 통해 컴포넌트가 제대로 작동하기 위한 추가 항목을 지정할 수 있습니다. 컴포넌트 중 하나에 종속성을 추가하면 Fuse가 이 종속성을 충족시킬 수 있도록 무언가를 지정해야 합니다. 그렇지 않으면 컴파일 오류가 발생합니다. 이렇게 하면 컴포넌트의 종속성이 항상 충족되고 올바른 코드를 확인하는 데 매우 유용합니다.

자, 우리의 HomePage에 라우터 의존성을 추가합시다. 이는 Router 인스턴스를 추가하는 것과 매우 유사하며 페이지 상단에 다음과 같이 입력합니다:

1
2
3
4
<Page ux:Class="HomePage">
    <Router />

    ...

종속성을 만들기 위한 방법은 ux:Dependency를 이용해 컴포넌트의 컨텍스트에 그 이름을 지정해 주는 것입니다. 예를 들어, 이 속성을 추가하고 우리의 의존성 라우터를 호출합시다:

1
2
3
4
<Page ux:Class="HomePage">
    <Router ux:Dependency="router" />

    ...

이제 Router 인스턴스를 생성하는 대신 Fuse는 컴포넌트 인스턴스를 생성할 때 라우터를 지정할 것입니다. 이것은 의존성을 지정하는 것 외에도 MainView의 라우터를 이 종속성에 연결해야 함을 의미합니다.

이를 위해 MainView.ux로 돌아갑니다. 먼저 ux:Name 속성을 사용하여 라우터에 이름을 지정합니다:

1
2
3
4
<App>
    <Router ux:Name="router" />

    ...

이름을 지정함으로써 이 라우터 인스턴스를 쉽게 참조할 수 있습니다. 다음으로, 우리는 이 인스턴스가 다음과 같이 HomePage 인스턴스에 제공되는지 확인합니다:

1
2
3
4
5
6
7
8
<App>
    <Router ux:Name="router" />

    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" router="router" />

            ...

홈페이지 인스턴스에 router=”router”를 추가함으로써, 우리는 홈페이지에 라우터 의존성을 전달했습니다. 의존성을 위해 소문자 이름을 사용하는 것을 제외하고는 속성 값을 지정하는 것과 비슷합니다. 또한 이 값을 지정하지 않으면 컴포넌트의 종속성이 충족되지 않아 컴포넌트가 제대로 작동하지 않기 때문에 오류가 발생합니다.

이제 MainView.ux와 HomePage.ux를 모두 저장할 수 있습니다. 프리뷰를 실행하는 경우 퓨즈는 파일을 저장하는 동안 오류가 발생할 수 있습니다. 파일을 저장하는 순서에 따라 어떤 오류가 발생할 수 있으며, 퓨즈가 우리의 의존성이나 이를 만족시키기 위해 지정한 값을 알지 못한다는 사실에서 비롯됩니다. 그러나 두 파일을 모두 저장 한 후에는 모든 것이 정상으로 돌아가고 HomePage는 라우터에 액세스할 수 있게 됩니다. 좋아요!

우리의 HomePage는 작업할 Router 인스턴스를 가지므로, 이 라우터를 사용하여 EditHikePage로 이동하기를 원할 것입니다. HomePage.js 파일을 살펴보면 ‘chooseHike’ 함수가 있다는 걸 발견할 수 있을 것입니다:

1
2
3
function chooseHike(arg) {
    // TODO
}

이 함수를 작성하기 전에 chooseHike에서 goToHike로 이름을 바꾸면 좋겠습니다. 이는 세부 사항이지만 기능의 의도를 더 잘 전달하고 작업을 조금 더 깨끗하게 유지합니다. 이름을 바꿉시다:

1
2
3
function goToHike(arg) {
    // TODO
}

아래 module.exports에서 이름을 업데이트합니다:

1
2
3
4
5
module.exports = {
    hikes: hikes,

    goToHike: goToHike
};

마지막으로, HomePage.ux에서 참조를 업데이트합니다:

1
2
3
            <Each Items="{hikes}">
                <Button Text="{name}" Clicked="{goToHike}" />
            </Each>

더 좋아졌네요! 이제 파일을 저장할 수 있으며 HomePage.js로 돌아가서 goToHike 함수를 작성할 준비가 되었습니다.

우선 우리가 해야할 일은 함수의 인자로 하이킹 객체를 받는 것입니다. 우리는 2장에서 봤던 것처럼 정확하게 다음과 같이 할 것입니다:

1
2
3
function goToHike(arg) {
    var hike = arg.data;
}

이제 하이킹 객체를 얻었고 EditHikePage로 이동하기 위해 UX의 라우터 종속성이 필요합니다. 다행스럽게도, 퓨즈에서 ux:Dependency의 값은 그 이름을 이용해 JavaScript에서도 참조할 수 있습니다. 따라서 HomePage.js의 라우터를 참조하면 실제로 MainView.ux에서 HomePage.ux의 ux:Dependency를 통해 전달된 라우터를 알 수 있습니다:

1
2
3
4
function goToHike(arg) {
    var hike = arg.data;
    router // 라우터가 있긴 한데, 어떻게 하나요?
}

그건 쉽습니다! 이제 우리가 해야할 일은 라우터에게 EditHikePage로 이동하라고 하는 것입니다. 이를 위해 우리는 push라는 JavaScript 함수를 사용합니다:

1
2
3
4
function goToHike(arg) {
    var hike = arg.data;
    router.push("editHike");
}

푸시 기능은 지정된 경로로 이동시킵니다. 이 경우에는 단순히 “editHike” 입니다. 이것은 라우터가 editHike로 이동한다는 것을 의미합니다. MainView.ux의 네비게이터와 템플릿을 기억한다면, 우리가 원했던 것처럼 EditHikePage의 인스턴스로 지정할 것입니다! HomePage.js를 저장하고 하이킹 중 하나를 선택하면 우리 앱이 예상했던 것처럼 EditHike 페이지로 이동합니다. 물론, 우리는 아직 실제로 하이킹 객체를 이 페이지로 보내지는 않았지만, 곧 그 사실을 알게 될 것입니다.

HomePage에서 EditHikePage로 이동한 것은 좋지만, 계속 진행하기 전에 HomePage로 돌아갈 수 있는 방법이 필요합니다. 원래 디자인을 보면 저장 버튼과 취소 버튼이 있음을 알 수 있습니다:

그러나 아직 모델을 변경하지 않았으므로 Back 버튼을 만드는 것부터 시작하겠습니다. 나중에 Cancel 버튼으로 쉽게 컨버팅할 수 있습니다.

먼저 HomePage에서와 마찬가지로 EditHikePage에 라우터 종속성을 추가해야 합니다. EditHikePage.ux에 종속성을 추가합니다:

1
2
3
4
<Page ux:Class="EditHikePage">
    <Router ux:Dependency="router" />

    ...

그리고 MainView.ux에서 우리는 그 의존성을 만족시킬 것입니다:

1
2
3
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" router="router" />
            <EditHikePage ux:Template="editHike" router="router" />

아주 쉽습니다! 이 파일을 저장한 다음 EditHikePage.ux로 돌아가서 하단에 간단한 Back 버튼을 만듭니다:

1
2
3
4
5
6
7
8
            ...

            <TextView Value="{comments}" TextWrapping="Wrap" />

            <Button Text="Back" />
        </StackPanel>
    </ScrollView>
</Page>

우리가 아직 참조할 함수를 만들지는 않았지만 Clicked 핸들러를 추가해 보겠습니다. (바로 뒤에):

1
2
3
4
5
6
7
8
            ...

            <TextView Value="{comments}" TextWrapping="Wrap" />

            <Button Text="Back" Clicked="{goBack}" />
        </StackPanel>
    </ScrollView>
</Page>

핸들러는 goBack 함수입니다. EditHikePage.js에 이 함수를 만들고 내보내기에 추가하십시오:

1
2
3
4
5
6
7
8
9
10
11
...

function goBack() {
    // TODO
}

module.exports = {
    ...

    goBack: goBack
};

이제 이 함수를 작성할 준비가 되었습니다. 이것은 정말 간단합니다:

1
2
3
function goBack() {
    router.goBack();
}

여기서 우리는 Router 객체가 goBack 함수를 호출하여 이전에 있었던 경로로 돌아가도록 하고 있습니다. 이는 라우터가 앱에서 이동될 때 라우터의 기록을 추적할 수 있기 때문이며 푸시 기능을 사용하여 이 페이지로 이동했기 때문에 이것이 가능합니다. 흥미롭게도 우리가 라우터의 goto 함수를 사용했다면 EditHikePage에 접근할 수는 있어도 히스토리를 추적하지는 못했을 것입니다.

참고: goBack은 자동으로 디바이스에서 기본으로 제공되는 Back 버튼에 연결됩니다 (적용 가능한 경우). Cmd + B (OS X) 또는 Control + B (Windows)를 사용하여 PC 프리뷰에서 시뮬레이션 할 수 있습니다.

마지막으로, 이 모든 것을 저장하고 Back 버튼이 실제로 작동하는지 확인하십시오! 이제 우리는 이 조각들이 어떻게 함께 작동하는지 이해하기 시작했습니다.

페이지 간에 데이터 보내기

우리가 해야할 마지막 작업은 하이킹을 네비게이션과 함께 EditHikePage에 보내는 것입니다. 이 부분은 실제로 두 가지 작업이 있습니다. HomePage에서 하이킹을 보내고 EditHikePage에서 하이킹을 받는 것입니다. 하이킹을 보내는 부분부터 시작하겠습니다.

이 부분은 꽤 쉬울 것입니다. HomePage.js로 돌아가보면 goToHike 함수에 이미 대부분의 코드가 있습니다:

1
2
3
4
function goToHike(arg) {
    var hike = arg.data;
    router.push("editHike");
}

우리가 해야 할 일은 push를 호출할 때 하이킹 객체를 추가하여 라우터가 이 데이터를 전달하게 하는 것 뿐입니다. 이것은 하이킹 객체가 ‘plain old data’이기 때문에 효과가 있습니다. 그것은 단순한 문자열 데이터, 함수 또는 그와 같은 어떤 팬시한 것들을 말합니다. 이것은 라우터를 통해 전송되는 객체에 대한 요구 사항입니다.

하이킹을 보내기 위해 다음과 같이 push 함수를 호출하여 하이킹 객체를 전달합니다:

1
2
3
4
function goToHike(arg) {
    var hike = arg.data;
    router.push("editHike", hike);
}

이 방법으로 우리는 라우터에게 editHike 라우트로 이동하여 하이킹 객체를 거기에 있는 타겟(여기서는 EditHikePage 인스턴스)에 전달하도록 지시합니다.

참고: 이 튜토리얼에서는 간단한 탐색만 사용하지만 다단계 탐색도 수행할 수 있습니다. 자세한 정보는 네비게이션 문서의 다중 레벨 탐색 섹션을 참조하십시오!

이제 하이킹 데이터를 EditHikePage에 보냈으므로 EditHikePage에 해당 데이터를 받기 위한 코드를 추가해야 합니다. 퓨즈는 이 목적을 위해 특별한 도구를 제공합니다. 자바스크립트에서, JS 모듈로 사용할 수 있는 Parameter라 불리는 Observable에 액세스할 수 있습니다. 이 Observable 파라미터는 라우터를 통해 컴포넌트로 전달되는 모든 수신 값을 나타냅니다. 이것을 사용하기 위해 우리가 이미 EditHikePage.js에 가지고 있는 Observable 하이킹을 살펴보겠습니다:

1
2
3
4
5
var Observable = require("FuseJS/Observable");

var hike = Observable();

...

이상적으로, 우리가 하고자 하는 것은 이 하이킹 Observable에 Parameter Observable을 통해 들어오는 모든 값을 채우는 것입니다. 우리가 할 수 있는 몇 가지 방법이 있지만 가장 간단한 방법은 다음과 같이 하는 것입니다:

1
2
3
var hike = this.Parameter;

...

사실, Observable을 새로 만들 필요가 없습니다. 우리는 하이킹 객체를 받았으므로 Parameter를 사용할 수 있습니다. var Observable = require(“FuseJS/Observable”);은 더 이상 필요하지 않습니다. 이것은 일을 정말 간단하게 만듭니다!

참고: Parameter는 그냥 Parameter가 아니라 this.Parameter를 사용하여 참조됩니다. 이는 Parameter가 모듈 정의의 일부이고 EditHikePage의 이 특정 인스턴스를 참조하기 때문에 이것이 이를 사용하여 정규화되어야 하기 때문입니다. 또한 JavaScript에서 사용되는 위치에 따라 다른 의미를 가질 수 있으므로 모듈의 루트에서 올바른 인스턴스를 사용하는 것이 중요합니다.

이제 이 모든 것을 저장하면 EditHikeView에 최종적으로 데이터가 다시 채워지는 것을 볼 수 있습니다! 우리는 페이지 사이를 오갈 수 있고 EditHikePage가 매번 정확한 하이킹의 데이터로 채워지는 것을 볼 수 있습니다. 정말 멋지죠!

우리의 진행 상황

이 시점에서, 우리는 탐색과 데이터 사이에 데이터를 전달하는 두 가지 컴포넌트를 모두 갖게 되었습니다! 우리의 앱은 현재 다음과 같습니다:

이 챕터에서 수정한 다양한 파일의 코드는 다음과 같습니다:

MainView.ux

1
2
3
4
5
6
7
8
9
10
<App>
    <Router ux:Name="router" />

    <ClientPanel>
        <Navigator DefaultPath="home">
            <HomePage ux:Template="home" router="router" />
            <EditHikePage ux:Template="editHike" router="router" />
        </Navigator>
    </ClientPanel>
</App>

Pages/HomePage.ux:

1
2
3
4
5
6
7
8
9
10
11
12
13
<Page ux:Class="HomePage">
    <Router ux:Dependency="router" />

    <JavaScript File="HomePage.js" />

    <ScrollView>
        <StackPanel>
            <Each Items="{hikes}">
                <Button Text="{name}" Clicked="{goToHike}" />
            </Each>
        </StackPanel>
    </ScrollView>
</Page>

Pages/HomePage.js:

1
2
3
4
5
6
7
8
9
10
11
12
var hikes = require("hikes");

function goToHike(arg) {
    var hike = arg.data;
    router.push("editHike", hike);
}

module.exports = {
    hikes: hikes,

    goToHike: goToHike
};

Pages/EditHikePage.ux:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Page ux:Class="EditHikePage">
    <Router ux:Dependency="router" />

    <JavaScript File="EditHikePage.js" />

    <ScrollView>
        <StackPanel>
            <Text Value="{name}" />

            <Text>Name:</Text>
            <TextBox Value="{name}" />

            <Text>Location:</Text>
            <TextBox Value="{location}" />

            <Text>Distance (km):</Text>
            <TextBox Value="{distance}" InputHint="Decimal" />

            <Text>Rating:</Text>
            <TextBox Value="{rating}" InputHint="Integer" />

            <Text>Comments:</Text>
            <TextView Value="{comments}" TextWrapping="Wrap" />

            <Button Text="Back" Clicked="{goBack}" />
        </StackPanel>
    </ScrollView>
</Page>

Pages/EditHikePage.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var hike = this.Parameter;

var name = hike.map(function(x) { return x.name; });
var location = hike.map(function(x) { return x.location; });
var distance = hike.map(function(x) { return x.distance; });
var rating = hike.map(function(x) { return x.rating; });
var comments = hike.map(function(x) { return x.comments; });

function goBack() {
    router.goBack();
}

module.exports = {
    name: name,
    location: location,
    distance: distance,
    rating: rating,
    comments: comments,

    goBack: goBack
};

다음으로 진행할 일

우리의 컴포넌트는 멋지게 작동합니다. 그러나 모델에서 실제 데이터를 변경할 수는 없습니다. 우리는 뷰 모델에서 일부 데이터만 변경할 수 있습니다. 다음 챕터에서는 이것을 정렬하기 위해 백엔드를 흉내내는 작업을 할 것입니다. 이렇게하면 향후 어느 시점에 실제 백엔드를 쉽게 추가할 수 있도록 앱 아키텍처를 마무리할 수 있습니다. 함께 갑시다!

이 챕터의 최종 코드는 여기에서 볼 수 있습니다.

Was this article helpful to you? Yes No

How can we help?