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

  1. Home
  2. 퓨즈[Fusetools] 문서[Docs] 번역
  3. 튜토리얼
  4. 2. 여러개의 하이킹(hikes)

2. 여러개의 하이킹(hikes)

소개

이전 챕터에서, 앱의 첫 번째 뷰인 ‘하이킹(Hike) 편집 화면’을 만들었습니다. 그것은 하나의 하이킹을 표시하고 편집하는 간단한 뷰입니다. 이것은 좋은 출발점이긴 하지만, 우리의 앱은 많은 하이킹 중 하나를 골라서 개별적으로 편집할 수 있어야 합니다.

이제 이 챕터에서 수행할 일을 설명하겠습니다. 관리하기 쉽도록 하기 위해 MainView.ux 파일에 모든 것을 담을 것입니다. 뷰 상단에 “selector”를 추가하여 개별 하이킹을 선택할 수 있고, 이전 챕터에서 만든 뷰에 해당 데이터가 채워질 것입니다. 일단 수정한 내용이 유지되지는 않고 단지 뷰 모델로 로드하는 것만 진행합니다. 이것은 ‘백엔드 흉내내기’에서 해결할 것입니다.

이 챕터의 전체 코드는 여기에서 받을 수 있습니다.

하이킹 리스트 생성하기

하이킹 리스트를 표시하기 전에 하이킹 리스트 데이터가 필요합니다. 여기서 우리는 모델처럼 보이는 것을 만들 것입니다. 처음에는 단순한 배열을 사용합니다. MainView.ux 파일의 JavaScript 상단에 다음 코드를 추가합니다:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var hikes = [
    {
        id: 0,
        name: "Tricky Trails",
        location: "Lakebed, Utah",
        distance: 10.4,
        rating: 4,
        comments: "This hike was nice and hike-like. Glad I didn't bring a bike."
    },
    {
        id: 1,
        name: "Mondo Mountains",
        location: "Black Hills, South Dakota",
        distance: 20.86,
        rating: 3,
        comments: "Not the best, but would probably do again. Note to self: don't forget the sandwiches next time."
    },
    {
        id: 2,
        name: "Pesky Peaks",
        location: "Bergenhagen, Norway",
        distance: 8.2,
        rating: 5,
        comments: "Short but SO sweet!!"
    },
    {
        id: 3,
        name: "Rad Rivers",
        location: "Moriyama, Japan",
        distance: 12.3,
        rating: 4,
        comments: "Took my time with this one. Great view!"
    },
    {
        id: 4,
        name: "Dangerous Dirt",
        location: "Cactus, Arizona",
        distance: 19.34,
        rating: 2,
        comments: "Too long, too hot. Also that snakebite wasn't very fun."
    }
];

이 배열의 각 항목에는 ‘하이킹(Hike) 편집 화면’과 동일한 필드가 있으며 추가된 id 필드는 각 항목의 고유한 식별자를 나타냅니다.

하이킹 리스트 표시하기

하이킹 리스트를 만들었으니, 그것들을 간단하게 표시하겠습니다.

처음 할 일은 UX에서 hikes 값을 사용할 수 있게 하는 것입니다:

1
2
3
4
5
6
7
8
9
module.exports = {
    hikes: hikes,

    name: name,
    location: location,
    distance: distance,
    rating: rating,
    comments: comments
};

표시된 하이킹 아이템을 선택할 수 있도록 하기 위해 버튼을 이용합니다. 이 버튼을 누르면 ‘하이킹(Hike) 편집 화면’을 채웁니다.

먼저, 하이킹 배열을 버튼으로 표시합니다. 하지만 그걸 UX에서 어떻게 할 수 있죠? 이를 위해 UX는 Each라 불리는 메커니즘을 제공합니다:

1
2
3
4
5
6
<ScrollView>
    <StackPanel>
        <Each Items="{hikes}">
        </Each>

        <Text Value="{name}" />

Each는 매우 파워풀한 UX 기능입니다. Each는 Items 속성에 지정된 컬렉션을 가져와서 각 항목을 Each 태그 하위 노드에 전달합니다. Items의 각 항목에 대해 Each 안의 코드를 복사하여 붙여 넣는 것과 같은 것으로 생각할 수 있습니다.

다음과 같이 Each를 사용하여 hikes의 name을 버튼의 Text 속성에 설정합니다:

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

이제 저장하면 각 항목에 대한 버튼들이 생성될 것입니다. 멋지죠! 이것은 우리가 Each 안에 넣을 수 있는 거의 모든 코드에서 작동하지만, 우리의 경우 각 하이킹에 대해 하나의 버튼만 있으면 됩니다.

우리가 어떻게 각 Button의 Text 속성에 name을 바인딩했는지 주목하십시오. 우리는 name이라는 변수를 노출하지 않았습니다. Each의 멋진 점 중 하나는 각 항목에 대해 Each가 바인딩하려는 데이터 컨텍스트를 현재 항목으로 “좁힌다(narrow down)”는 것입니다. 그래서 우리가 name에 바인딩할 때, JS에서 export된 name이라는 변수에 바인딩하지 않습니다. 우리는 현재 항목의 name 속성에 바인딩합니다. 이 경우에는 현재 하이킹 아이템이죠. 아주 쉽습니다!

하이킹 선택하기

모델을 통해 버튼을 만들었으므로 하나씩 선택할 수 있습니다.

현재 우리가 만든 뷰 모델은 편집 가능한 다양한 필드에 바인딩된 Observable로 구성되어 있습니다. 현재 편집중인 하이킹을 나타내는 Observable을 추가합시다. 처음에는 하이킹을 선택하지 않았으니 이 항목들은 비어 있습니다.

1
var hike = Observable();

이제 데이터를 다른 Observable의 데이터로부터 가져와야 합니다. 하이킹이 선택될 때마다 Observable의 필드를 채울 수 있지만, 이것은 Observable을 감시해야 한다는 것을 의미하고 솔직히 제 취향은 아닙니다. 우리는 대신 선언적으로 처리하길 원합니다!

오히려 우리가 할 일은 관찰 가능한 하이킹의 값을 다양한 속성의 값으로 변환하거나 매핑하는 것입니다. 예를 들어 name이 다음과 같이 표시될 수 있습니다.

1
var name = hike.map(function(x) { return x.name; });

함수형 반응 프로그래밍에 익숙하지 않은 경우 처음에는 조금 어려울 수 있지만 걱정마십시오. 언젠가 느낌이 오면 더 직관적이라 생각할 것입니다.

이 코드를 나눠서 보면 몇 가지 사실을 알게될 것입니다. 우선, 우리는 하이킹에 map 함수를 호출합니다. map은 다른 Observable과 똑같이 작동하고 값이 변형된 새로운 Observable을 반환할 것입니다. 즉, name의 모든 값을 기준으로 하이킹 값에서 매핑됩니다.

또한 map에 함수를 전달한 것을 볼 수 있습니다. 이 함수는 (당연하겠지만) 매핑 함수라고 합니다. 매핑 함수는 다른 함수와 마찬가지로 – 인수를 사용하여 해당 인수를 기반으로 새 값을 반환합니다. 때로는 이러한 기능에 부작용이 있어서 사이드 이펙트가 발생하긴 합니다. 하지만 우리가 사용한 함수는 인수를 기반으로 값을 반환하는 순수한 함수이므로 문제없습니다.

그런데 이 매핑 함수가 정확히 무슨 일을 하죠? 좋은 질문입니다! 간단히 말해, 하이킹(hike)에 새로운 값이 들어올 때마다 매핑 함수가 호출됩니다. 이 새로운 값은 인수로 함수에 전달되고요. 함수는 이 인수를 기반으로 새로운 값을 반환합니다. 결국 이 값이 우리가 만드는 Observable의 값이 됩니다.

따라서, 이것은 우리가 하이킹(hike)을 선택했을 때 하이킹에 들어가는 각 값의 name 속성을 나타내는 새로운 Observable을 만들게 됩니다. 멋집니다. 그렇죠?

참고: Observable Crash Course 비디오 및 Observable 문서에서 Observables에 대한 자세한 정보를 확인할 수 있습니다.

자, name과 같이 다른 Observable도 리팩토링 해봅시다:

1
2
3
4
5
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; });

마지막으로, 각 버튼을 눌렀을 때 하이킹(hike) Observable에 값을 넣을 함수가 필요합니다. (차례대로 모든 필드의 Observable을 채웁니다) 자바스크립트에서 빈 함수를 만드는 것으로 시작하겠습니다. 다음과 같이 작성하세요:

1
2
function chooseHike() {
}

다음으로, UX에서 접근이 가능하도록 exports에 넣겠습니다:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
    hikes: hikes,

    name: name,
    location: location,
    distance: distance,
    rating: rating,
    comments: comments,

    chooseHike: chooseHike
};

그리고 다음과 같이 버튼들을 연결합니다:

1
<Button Text="{name}" Clicked="{chooseHike}" />

이제 함수를 채워넣어야 할 때입니다. 하이킹(hike) Observable의 값을 채워넣을 계획입니다:

1
2
3
function chooseHike() {
    hike.value = ???
}

그런데 정확히 뭘 넣어야 하죠? 우리가 버튼을 클릭해서 함수를 바인딩할 때, 그 함수는 인수를 받을 수 있습니다. 이 인수는 버튼의 현재 데이터 컨텍스트를 나타내는 데이터 필드를 포함합니다. 그리고 우리가 Each를 사용했으므로 데이터는 실제로 우리가 원하는 하이킹이 될 것입니다. 멋지죠! 이제 인수를 받고 data 속성을 하이킹(hike) Observable에 넣기 위해 함수를 업데이트합시다:

1
2
3
function chooseHike(arg) {
    hike.value = arg.data;
}

파일을 저장하면 하이킹 셀렉터(selectors)가 제대로 동작하는걸 확인할 수 있습니다! 그 중 하나를 클릭하면 ‘하이킹(Hike) 편집 화면’이 채워지고 개별 필드를 편집할 수 있습니다. 좋아요!

우리의 진행 상황

개별 하이킹을 보여주고 편집할 수 있는 여러개의 하이킹으로 구성된 기본적인 모델을 만들었습니다. 전체적으로는 다음과 같은 모습입니다:

코드량은 많이 늘지 않았습니다. 다음과 같습니다:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<App>
    <ClientPanel>
        <JavaScript>
            var Observable = require("FuseJS/Observable");

            var hikes = [
                {
                    id: 0,
                    name: "Tricky Trails",
                    location: "Lakebed, Utah",
                    distance: 10.4,
                    rating: 4,
                    comments: "This hike was nice and hike-like. Glad I didn't bring a bike."
                },
                {
                    id: 1,
                    name: "Mondo Mountains",
                    location: "Black Hills, South Dakota",
                    distance: 20.86,
                    rating: 3,
                    comments: "Not the best, but would probably do again. Note to self: don't forget the sandwiches next time."
                },
                {
                    id: 2,
                    name: "Pesky Peaks",
                    location: "Bergenhagen, Norway",
                    distance: 8.2,
                    rating: 5,
                    comments: "Short but SO sweet!!"
                },
                {
                    id: 3,
                    name: "Rad Rivers",
                    location: "Moriyama, Japan",
                    distance: 12.3,
                    rating: 4,
                    comments: "Took my time with this one. Great view!"
                },
                {
                    id: 4,
                    name: "Dangerous Dirt",
                    location: "Cactus, Arizona",
                    distance: 19.34,
                    rating: 2,
                    comments: "Too long, too hot. Also that snakebite wasn't very fun."
                }
            ];

            var hike = Observable();

            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 chooseHike(arg) {
                hike.value = arg.data;
            }

            module.exports = {
                hikes: hikes,

                name: name,
                location: location,
                distance: distance,
                rating: rating,
                comments: comments,

                chooseHike: chooseHike
            };
        </JavaScript>

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

                <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" />
            </StackPanel>
        </ScrollView>
    </ClientPanel>
</App>

이제 우리는 퓨즈 앱을 통해 데이터가 어떻게 흘러가는지, 서로 다른 뷰가 어떻게 상호 작용하는지에 대한 감을 잡았습니다.

다음으로 진행할 일

하이킹 목록에서 하나씩 선택해서 볼 수 있는건 정말 유용하지만, 이 뷰의 일부를 별도로 분리하는 것이 이상적입니다. 다음 챕터에서 우리가 할 일이 바로 그것입니다. 뷰와 뷰 모델을 분리하고, 모델을 조직화 및 별도로 구성된 컴포넌트로 분리할 것입니다. 함께 가시죠!

최종 코드는 여기에 있습니다.

Was this article helpful to you? Yes No

How can we help?